From 4816ba96ff5e5d80eef6e2a4fba6b773b0a22aef Mon Sep 17 00:00:00 2001 From: Fayvor Love Date: Fri, 30 Aug 2024 14:18:51 -0400 Subject: [PATCH 1/3] Update plot_predictions to plot history + prediction Signed-off-by: Fayvor Love --- ...and_forecast_zeroshot_recipe_minimal.ipynb | 580 ++++++++++++++++++ tsfm_public/toolkit/visualization.py | 85 ++- 2 files changed, 646 insertions(+), 19 deletions(-) create mode 100644 notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb diff --git a/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb b/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb new file mode 100644 index 00000000..e51db0bb --- /dev/null +++ b/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb @@ -0,0 +1,580 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "b6b03c92-c01f-4974-a850-42268c65117d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Granite-TimeSeries-TTM \n", + "\n", + "TinyTimeMixers (TTMs) are compact pre-trained models for Multivariate Time-Series Forecasting, open-sourced by IBM Research. With less than 1 Million parameters, TTM introduces the notion of the first-ever \"tiny\" pre-trained models for Time-Series Forecasting. TTM outperforms several popular benchmarks demanding billions of parameters in zero-shot and few-shot forecasting and can easily be fine-tuned for multi-variate forecasts." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e7deb64f-9f1a-4f20-aa1d-01b46abfa7d5", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:p-20565:t-8607625792:config.py::PyTorch version 2.2.2 available.\n" + ] + } + ], + "source": [ + "import pathlib\n", + "import pandas as pd\n", + "from tsfm_public import TimeSeriesForecastingPipeline, TinyTimeMixerForPrediction\n", + "from tsfm_public.toolkit.visualization import plot_predictions" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "306a8c42-3d2f-4511-baa5-ce985d54c38f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0.2.9.dev29+g6f55407.d20240830'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import tsfm_public\n", + "tsfm_public.__version__" + ] + }, + { + "cell_type": "markdown", + "id": "73406eda-65aa-438e-aee6-9c65f1a3ee56", + "metadata": {}, + "source": [ + "## Initial setup\n", + "1. Download energy_data.csv.zip and weather_data.csv.zip from https://www.kaggle.com/datasets/nicholasjhana/energy-consumption-generation-prices-and-weather\n", + "2. Place the downloaded files into a folder and update the data_path below" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1563d66d-bf38-4fcf-bdd0-57a9187ef8e4", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = pathlib.Path(\"~/Dev/data\")" + ] + }, + { + "cell_type": "markdown", + "id": "d0ce984c", + "metadata": {}, + "source": [ + "## Load and prepare data" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "984ca0d9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(512, 29)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
timegeneration biomassgeneration fossil brown coal/lignitegeneration fossil coal-derived gasgeneration fossil gasgeneration fossil hard coalgeneration fossil oilgeneration fossil oil shalegeneration fossil peatgeneration geothermal...generation wastegeneration wind offshoregeneration wind onshoreforecast solar day aheadforecast wind offshore eday aheadforecast wind onshore day aheadtotal load forecasttotal load actualprice day aheadprice actual
345522018-12-10 16:00:00+01:00308.0683.00.03978.03080.0306.00.00.00.0...289.00.05746.02494.0NaN6466.024484.024465.056.9669.76
345532018-12-10 17:00:00+01:00314.0686.00.04338.03241.0303.00.00.00.0...289.00.05524.01838.0NaN6269.024033.024068.067.3273.48
345542018-12-10 18:00:00+01:00313.0711.00.05020.03436.0305.00.00.00.0...295.00.05139.01119.0NaN5962.024053.024018.068.6877.65
345552018-12-10 19:00:00+01:00315.0716.00.05449.03410.0294.00.00.00.0...297.00.04933.0404.0NaN5690.025203.025036.070.4676.23
345562018-12-10 20:00:00+01:00316.0711.00.05645.03419.0295.00.00.00.0...294.00.04929.0200.0NaN5680.027579.027411.072.8275.54
\n", + "

5 rows × 29 columns

\n", + "
" + ], + "text/plain": [ + " time generation biomass \\\n", + "34552 2018-12-10 16:00:00+01:00 308.0 \n", + "34553 2018-12-10 17:00:00+01:00 314.0 \n", + "34554 2018-12-10 18:00:00+01:00 313.0 \n", + "34555 2018-12-10 19:00:00+01:00 315.0 \n", + "34556 2018-12-10 20:00:00+01:00 316.0 \n", + "\n", + " generation fossil brown coal/lignite \\\n", + "34552 683.0 \n", + "34553 686.0 \n", + "34554 711.0 \n", + "34555 716.0 \n", + "34556 711.0 \n", + "\n", + " generation fossil coal-derived gas generation fossil gas \\\n", + "34552 0.0 3978.0 \n", + "34553 0.0 4338.0 \n", + "34554 0.0 5020.0 \n", + "34555 0.0 5449.0 \n", + "34556 0.0 5645.0 \n", + "\n", + " generation fossil hard coal generation fossil oil \\\n", + "34552 3080.0 306.0 \n", + "34553 3241.0 303.0 \n", + "34554 3436.0 305.0 \n", + "34555 3410.0 294.0 \n", + "34556 3419.0 295.0 \n", + "\n", + " generation fossil oil shale generation fossil peat \\\n", + "34552 0.0 0.0 \n", + "34553 0.0 0.0 \n", + "34554 0.0 0.0 \n", + "34555 0.0 0.0 \n", + "34556 0.0 0.0 \n", + "\n", + " generation geothermal ... generation waste generation wind offshore \\\n", + "34552 0.0 ... 289.0 0.0 \n", + "34553 0.0 ... 289.0 0.0 \n", + "34554 0.0 ... 295.0 0.0 \n", + "34555 0.0 ... 297.0 0.0 \n", + "34556 0.0 ... 294.0 0.0 \n", + "\n", + " generation wind onshore forecast solar day ahead \\\n", + "34552 5746.0 2494.0 \n", + "34553 5524.0 1838.0 \n", + "34554 5139.0 1119.0 \n", + "34555 4933.0 404.0 \n", + "34556 4929.0 200.0 \n", + "\n", + " forecast wind offshore eday ahead forecast wind onshore day ahead \\\n", + "34552 NaN 6466.0 \n", + "34553 NaN 6269.0 \n", + "34554 NaN 5962.0 \n", + "34555 NaN 5690.0 \n", + "34556 NaN 5680.0 \n", + "\n", + " total load forecast total load actual price day ahead price actual \n", + "34552 24484.0 24465.0 56.96 69.76 \n", + "34553 24033.0 24068.0 67.32 73.48 \n", + "34554 24053.0 24018.0 68.68 77.65 \n", + "34555 25203.0 25036.0 70.46 76.23 \n", + "34556 27579.0 27411.0 72.82 75.54 \n", + "\n", + "[5 rows x 29 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Download energy_data.csv.zip from https://www.kaggle.com/datasets/nicholasjhana/energy-consumption-generation-prices-and-weather\n", + "\n", + "dataset_path = data_path / \"energy_dataset.csv.zip\"\n", + "timestamp_column = \"time\"\n", + "\n", + "target_column = \"total load actual\"\n", + "\n", + "context_length = 512 # set by the pretrained model we will use\n", + "\n", + "data = pd.read_csv(\n", + " dataset_path,\n", + " parse_dates=[timestamp_column],\n", + ")\n", + "\n", + "data = data.ffill()\n", + "\n", + "data = data.iloc[-context_length:,]\n", + "\n", + "print(data.shape)\n", + "data.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "75c2d666-2404-4e78-b564-ea8aec8afa2d", + "metadata": {}, + "source": [ + "## Load pretrained Granite-TimeSeries-TTM model (zero-shot)\n", + "The **TTM** model supports huggingface model interface, allowing easy API for loading the saved models." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "eed3fe8b-b654-4fa4-9671-ce5ecd9e0b7b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TTM Model parameters: 805280\n" + ] + } + ], + "source": [ + "zeroshot_model = TinyTimeMixerForPrediction.from_pretrained(\n", + " \"ibm-granite/granite-timeseries-ttm-v1\", num_input_channels=1\n", + ")\n", + "model_parameters = sum(p.numel() for p in zeroshot_model.parameters() if p.requires_grad)\n", + "print(\"TTM Model parameters:\", model_parameters)" + ] + }, + { + "cell_type": "markdown", + "id": "b6ab206c", + "metadata": {}, + "source": [ + "### Create a time series forecasting pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d9aa0f26", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Hardware accelerator e.g. GPU is available in the environment, but no `device` argument is passed to the `Pipeline` object. Model will be on CPU.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
timetotal load actual_prediction
02019-01-01 00:00:00+01:0023504.996094
12019-01-01 01:00:00+01:0022338.626953
22019-01-01 02:00:00+01:0021448.902344
32019-01-01 03:00:00+01:0020982.527344
42019-01-01 04:00:00+01:0020697.185547
\n", + "
" + ], + "text/plain": [ + " time total load actual_prediction\n", + "0 2019-01-01 00:00:00+01:00 23504.996094\n", + "1 2019-01-01 01:00:00+01:00 22338.626953\n", + "2 2019-01-01 02:00:00+01:00 21448.902344\n", + "3 2019-01-01 03:00:00+01:00 20982.527344\n", + "4 2019-01-01 04:00:00+01:00 20697.185547" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pipeline = TimeSeriesForecastingPipeline(\n", + " zeroshot_model, timestamp_column=timestamp_column, target_columns=[target_column], explode_forecasts=True, freq=\"h\", id_columns=[]\n", + ")\n", + "zeroshot_forecast = pipeline(data)\n", + "zeroshot_forecast.head()" + ] + }, + { + "cell_type": "markdown", + "id": "5c4676bd", + "metadata": {}, + "source": [ + "### Plot the results" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ba065a24", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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vCaoTwBAYmIi1q9fj2effdbheuWFstwn6tevjyZNmhS/Pn36NHbt2oXOnTsX//bvvvtOsQ5RvvvE2bNnkZOTg9GjR6Nbt2548skncf78eYftKw+U5z4RERGB5OTk4s+zs7ORmZmJ9PR0h23UC73OReXKleHn56f4rdeuXQMAu7/V2f5cvceWtC+VV8pznzh79iz8/Pzw1FNPoWvXrnjkkUdw6NAhVdtWA4luwi1mzZqFY8eO4eWXX0ZOTg78/f0Vn/v7+yM/P1/Vti5duoSqVati+fLlWLZsGfLy8vDhhx9q0s5Nmzbhiy++wKuvvmrVRlcZOnQoVq9ejc6dO+Pxxx/H7du3NWljWaE89onVq1ejRYsWiplbglNW+0RaWhqef/55tGnTxirJHuGY8tQnzp07h8zMTDzzzDNYtGgRAgMDMXbsWHp2WFCe+sSAAQPw+eef4+zZs4q2FRQUaNLGkqLVufD19UXfvn0xZ84cJCUl4datW5g5cyZ8fX3t/taSnnu9t1deKU994vz588jMzMT999+PJUuWoH79+hgzZkzx5EBJIdFNuMysWbOwfPlyzJo1C40aNUJAQIDVBZCfn4/AwECn27p9+zbefPNNvPHGG+jYsSO6du2K999/H2vWrMH169cxdepUtG7duvgvMTFRdTs3bdqEl156CY888gjuv/9+AMBnn32m2N6+fftUb6927dpo2bIlPvroI+Tm5uKPP/5Q/d2yTnntE7///juGDBmiev3yRFntE6mpqRgzZgwkScK8efNgNNJjVC3lrU8sW7YMP//8M7p06YJWrVph9uzZyMvLw+bNm1W3paxT3vrEs88+ixYtWmDgwIFo27Yt/P390aRJE4SEhKhui15oeS4A4K233kJwcDB69OiBu+66C23atEHlypUREhJi89iVZH+//PKLYnu//PJLidtPlL8+8e6772LTpk3o06cPmjdvjunTp6NGjRpYu3atqt/nDF9NtkKUG9599118++23mDVrFvr16wcAiI6OxpkzZxTrpaamWrlw2OLcuXPIzs5WuIY0a9YMZrMZSUlJePHFF/HEE08Uf6ZmmwCwYcMGvP7663jggQcwZcqU4vcfeOAB3HvvvcWvo6OjnW5r8+bNaNasWfG6AQEBqFmzptfcwUob5bFPAMwt6syZM2TptEFZ7RPJycl49NFHATA30/DwcFX7Icpnn/D391dYVQICAlCjRg2Fe2V5pjz2iQoVKmDu3Lm4desWDAYDQkJC0LlzZ1SvXl1VW/RC63MBMLfdFStWICMjAwEBAZAkCR9//DGqV6+O1q1bWx276Ohoq3KsavfXu3dvhcdZREQEEhMTS9T+8k557BO+vr6KCTCDwYB69eppds+mKXpCNQsWLMB3332HOXPmYODAgcXvx8bG4ujRo8jNzS1+Lz4+XpXLrdzRxYvg3LlzAIAaNWogIiICtWvXLv7z9XU+T7Rr1y68/vrrePjhh/H2228rPgsNDVVsT81s2cyZMxX1l2/fvo0LFy6gfv36Tr9b1imvfQJgCTmqVq2KatWqqVq/vFBW+0R2djbGjRsHo9GIlStXqp6cIcpnn5AkCX369MGPP/5Y/F52djYuXryIevXqOW1LWac89gkA+Oijj/DTTz+hYsWKCAkJwaFDh3Dr1i2vJl/U41wAwMSJE7Fjxw6EhoYiKCgIW7duRUREBBo0aGDz2MXGxuLAgQOQJAkAu4b279+van8hISGK7YWEhJS4/eWZ8tonRo8ejQULFhR/ZjabcfLkSc3u2SS6CVWcPXsWixYtwpNPPom2bdsiJSWl+K9Dhw6oWrUqJk+ejNOnT2PJkiU4dOgQRo4c6XS7MTEx6N69O95++20cOXIEhw8fxttvv42BAwe6ZUUqLCzElClT0L59ezz55JOKdrobA/Lwww9j2bJl2Lp1K06fPo2JEyeiVq1auOuuu9zaXlmhPPcJgCXIoYkXJWW5T3z++ee4dOkSZs6cCQDF6zvKgE+U3z5hMBjQs2dPzJ8/H3v27MHp06fx+uuvIyYmBj169HC5fWWJ8tonADYxsGDBAhw6dAhHjhzBxIkT8eCDDyqStXkSvc4FwCYlPvnkE5w6dQp79uzBu+++i/Hjx9sNyenfvz9u3ryJ9957D2fOnMF7772HnJwchfXTFUra/vJKee4TvXv3xldffYW//voL586dw4wZM3Dr1i3cd999bu3PEnIvJ1Tx119/wWQyYfHixVi8eLHis5MnT2LRokV48803MXz4cNSuXRsLFy5UbQH8+OOP8eGHH2L8+PEwGAy4++678cYbb7jVziNHjiAxMRGJiYno1q2b4rMVK1agY8eOLm/z4YcfRk5ODqZPn460tDR07doVixcvLvexnOW5TwDMJaly5cpufbesUpb7xO+//47c3NzieE6Z++67T7NETWWR8twnJk6cCF9fX7z66qu4ffs2OnXqhCVLlsDHx8etNpYVynOfGD16NK5evYonn3wSRqMRQ4cOtVlizFPoeS5eeuklvPPOO3jooYdQoUIFjB071mEJzpCQEHz++eeYNm0afvjhBzRu3BhLlixRlHdyBR8fnxK1v7xSnvvE2LFjkZeXh//85z9ITU1FbGwsvvzyS81yLhgk2WZPEARBEARBEARBEISmlG9THUEQBEEQBEEQBEHoCIlugiAIgiAIgiAIgtAJEt0EQRAEQRAEQRAEoRMkugmCIAiCIAiCIAhCJ0h0EwRBEARBEARBEIROkOgmCIIgCIIgCIIgCJ0g0U0QBEEQBEEQBEEQOkGimyAIgiAIgiAIgiB0gkQ3QRAEQRAEQRAEQegEiW6CIAiCIAiCIAiC0AkS3QRBEARBEARBEAShEyS6CYIgCIIgCIIgCEIn/h+oBfVulN5rfQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_predictions(\n", + " predictions_df=zeroshot_forecast,\n", + " context_df=data,\n", + " freq=\"h\",\n", + " timestamp_column=timestamp_column,\n", + " channel=target_column,\n", + " indices=[-1],\n", + " num_plots=1\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5123e226-1a66-434c-a400-f7be59974d5d", + "metadata": {}, + "source": [ + "## Useful links\n", + "\n", + "TinyTimeMixer paper: https://arxiv.org/abs/2401.03955 \n", + "\n", + "Granite-TimeSeries-TTM model: https://huggingface.co/ibm-granite/granite-timeseries-ttm-v1 \n", + "\n", + "Publicly available tools for working with our models: https://github.com/ibm-granite/granite-tsfm" + ] + }, + { + "cell_type": "markdown", + "id": "53116412", + "metadata": {}, + "source": [ + "© 2024 IBM Corporation" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tsfm_public/toolkit/visualization.py b/tsfm_public/toolkit/visualization.py index c3d259c2..0b508f35 100644 --- a/tsfm_public/toolkit/visualization.py +++ b/tsfm_public/toolkit/visualization.py @@ -208,6 +208,7 @@ def plot_predictions( test_df: Optional[pd.DataFrame] = None, predictions_df: Optional[pd.DataFrame] = None, dset: Optional[Dataset] = None, + context_df: Optional[pd.DataFrame] = None, model: Optional[PreTrainedModel] = None, freq: Optional[str] = None, timestamp_column: Optional[str] = None, @@ -219,40 +220,72 @@ def plot_predictions( channel: Union[int, str] = None, indices: List[int] = None, ): - random_indices = indices + """Utility for plotting forecasts along with context and test data. - if random_indices is not None: - num_plots = len(random_indices) + Args: + test_df: Test data. + predictions_df: The predictions dataframe, containing timestamp and prediction columns + dset: Dataset. + context_df: Context dataframe, containing timestamp and target columns. + model: The pre-trained TimeseriesModel. + freq: Frequency of the time series data + timestamp_column: Name of timestamp column in the dataframe. + id_columns: List of id columns in the dataframe. + plot_context: If True, plot context data along with forecasts. + plot_dir: Directory where plots are saved. + num_plots: Number of subplots to plot in the figure. + plot_prefix: Prefix to put on the plot file names. + channel: Channel (target column or its index) to plot. + indices: List of indices to plot. + """ + if indices is not None: + num_plots = len(indices) # possible operations: - if test_df is not None and predictions_df is not None: - # 1) test_df and predictions plus column information is provided + if context_df is not None and predictions_df is not None: + # 1) This is a zero-shot prediction, so no test data. We have context data for the channel (target column). + # We expect the context and predictions to contain the channel + pchannel = f"{channel}_prediction" + if pchannel not in predictions_df.columns: + raise ValueError(f"Predictions dataframe does not contain target column '{pchannel}'.") + if channel not in context_df.columns: + raise ValueError(f"Context dataframe does not contain target column '{channel}'.") + + num_plots = 1 + prediction_length = len(predictions_df) + plot_context = len(context_df) + using_pipeline = True + plot_test_data = False + elif test_df is not None and predictions_df is not None: + # 2) test_df and predictions plus column information is provided - l = len(predictions_df) - if random_indices is None: - random_indices = np.random.choice(l, size=num_plots, replace=False) - predictions_subset = [predictions_df.iloc[i] for i in random_indices] + if indices is None: + l = len(predictions_df) + indices = np.random.choice(l, size=num_plots, replace=False) + predictions_subset = [predictions_df.iloc[i] for i in indices] gt_df = test_df.copy() gt_df = gt_df.set_index(timestamp_column) # add id column logic here prediction_length = len(predictions_subset[0][channel]) using_pipeline = True + plot_test_data = True elif model is not None and dset is not None: - # 2) model and dataset are provided + # 3) model and dataset are provided device = model.device with torch.no_grad(): - if random_indices is None: - random_indices = np.random.choice(len(dset), size=num_plots, replace=False) - random_samples = torch.stack([dset[i]["past_values"] for i in random_indices]).to(device=device) + if indices is None: + indices = np.random.choice(len(dset), size=num_plots, replace=False) + random_samples = torch.stack([dset[i]["past_values"] for i in indices]).to(device=device) output = model(random_samples) predictions_subset = output.prediction_outputs[:, :, channel].squeeze().cpu().numpy() prediction_length = predictions_subset.shape[1] using_pipeline = False + plot_test_data = True else: - raise RuntimeError("You must provide either test_df and predictions_df or dset and model.") + raise RuntimeError("You must provide either test_df and predictions_df, or dset and model, or context_df, predictions_df and target_columns.") if plot_context is None: plot_context = 2 * prediction_length @@ -266,8 +299,8 @@ def plot_predictions( if num_plots == 1: axs = [axs] - for i, ri in enumerate(random_indices): - if using_pipeline: + for i, index in enumerate(indices): + if using_pipeline and plot_test_data: ts_y_hat = create_timestamps(predictions_subset[i][timestamp_column], freq=freq, periods=prediction_length) y_hat = ( predictions_subset[i][f"{channel}_prediction"] @@ -282,9 +315,21 @@ def plot_predictions( ts_y = y.index y = y.values border = ts_y[-prediction_length] + plot_title = f"Example {indices[i]}" + + elif using_pipeline: + ts_y_hat = create_timestamps(predictions_df[timestamp_column].iloc[0], freq=freq, periods=prediction_length) + y_hat = predictions_df[f"{channel}_prediction"] + + # get context + # ts_y = create_timestamps(context_df[timestamp_column].iloc[0], freq=freq, periods=len(context_df)) + ts_y = context_df[timestamp_column].values + y = context_df[channel].values + border = None + plot_title = f"Forecast for {channel}" else: - batch = dset[ri] + batch = dset[index] ts_y_hat = np.arange(plot_context, plot_context + prediction_length) y_hat = predictions_subset[i] @@ -293,6 +338,7 @@ def plot_predictions( x = batch["past_values"][-plot_context:, channel].squeeze().numpy() y = np.concatenate((x, y), axis=0) border = plot_context + plot_title = f"Example {indices[i]}" # Plot predicted values with a dashed line axs[i].plot(ts_y_hat, y_hat, label="Predicted", linestyle="--", color="orange", linewidth=2) @@ -301,9 +347,10 @@ def plot_predictions( axs[i].plot(ts_y, y, label="True", linestyle="-", color="blue", linewidth=2) # Plot horizon border - axs[i].axvline(x=border, color="r", linestyle="-") + if border is not None: + axs[i].axvline(x=border, color="r", linestyle="-") - axs[i].set_title(f"Example {random_indices[i]}") + axs[i].set_title(plot_title) axs[i].legend() # Adjust overall layout From 85c46dd41a1ac2b48d46c447ab587d6a78174165 Mon Sep 17 00:00:00 2001 From: Fayvor Love Date: Fri, 30 Aug 2024 15:22:55 -0400 Subject: [PATCH 2/3] Accept both context and test data as input_df. Signed-off-by: Fayvor Love --- ...and_forecast_zeroshot_recipe_minimal.ipynb | 6 ++-- tsfm_public/toolkit/visualization.py | 35 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb b/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb index e51db0bb..0cd46783 100644 --- a/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb +++ b/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb @@ -27,7 +27,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO:p-20565:t-8607625792:config.py::PyTorch version 2.2.2 available.\n" + "INFO:p-26782:t-8661148224:config.py::PyTorch version 2.2.2 available.\n" ] } ], @@ -523,8 +523,8 @@ ], "source": [ "plot_predictions(\n", - " predictions_df=zeroshot_forecast,\n", - " context_df=data,\n", + " input_df=data,\n", + " exploded_predictions_df=zeroshot_forecast,\n", " freq=\"h\",\n", " timestamp_column=timestamp_column,\n", " channel=target_column,\n", diff --git a/tsfm_public/toolkit/visualization.py b/tsfm_public/toolkit/visualization.py index 0b508f35..d47ea0ee 100644 --- a/tsfm_public/toolkit/visualization.py +++ b/tsfm_public/toolkit/visualization.py @@ -205,10 +205,10 @@ def plot_ts_forecasting( def plot_predictions( - test_df: Optional[pd.DataFrame] = None, + input_df: Optional[pd.DataFrame] = None, predictions_df: Optional[pd.DataFrame] = None, + exploded_predictions_df: Optional[pd.DataFrame] = None, dset: Optional[Dataset] = None, - context_df: Optional[pd.DataFrame] = None, model: Optional[PreTrainedModel] = None, freq: Optional[str] = None, timestamp_column: Optional[str] = None, @@ -223,8 +223,9 @@ def plot_predictions( """Utility for plotting forecasts along with context and test data. Args: - test_df: Test data. - predictions_df: The predictions dataframe, containing timestamp and prediction columns + input_df: The input dataframe from which the predictions are generated, containing timestamp and target columns. + predictions_df: The predictions dataframe, where each row contains starting timestamp and a list of predictions for each target column. + exploded_predictions_df: The predictions dataframe, containing timestamp and predicted target columns. dset: Dataset. context_df: Context dataframe, containing timestamp and target columns. model: The pre-trained TimeseriesModel. @@ -242,29 +243,29 @@ def plot_predictions( num_plots = len(indices) # possible operations: - if context_df is not None and predictions_df is not None: + if input_df is not None and exploded_predictions_df is not None: # 1) This is a zero-shot prediction, so no test data. We have context data for the channel (target column). # We expect the context and predictions to contain the channel pchannel = f"{channel}_prediction" - if pchannel not in predictions_df.columns: + if pchannel not in exploded_predictions_df.columns: raise ValueError(f"Predictions dataframe does not contain target column '{pchannel}'.") - if channel not in context_df.columns: + if channel not in input_df.columns: raise ValueError(f"Context dataframe does not contain target column '{channel}'.") num_plots = 1 - prediction_length = len(predictions_df) - plot_context = len(context_df) + prediction_length = len(exploded_predictions_df) + plot_context = len(input_df) using_pipeline = True plot_test_data = False - elif test_df is not None and predictions_df is not None: - # 2) test_df and predictions plus column information is provided + elif input_df is not None and predictions_df is not None: + # 2) input_df and predictions plus column information is provided if indices is None: l = len(predictions_df) indices = np.random.choice(l, size=num_plots, replace=False) predictions_subset = [predictions_df.iloc[i] for i in indices] - gt_df = test_df.copy() + gt_df = input_df.copy() gt_df = gt_df.set_index(timestamp_column) # add id column logic here prediction_length = len(predictions_subset[0][channel]) @@ -285,7 +286,7 @@ def plot_predictions( using_pipeline = False plot_test_data = True else: - raise RuntimeError("You must provide either test_df and predictions_df, or dset and model, or context_df, predictions_df and target_columns.") + raise RuntimeError("You must provide either input_df and predictions_df, or dset and model, or input_df and exploded_predictions_df.") if plot_context is None: plot_context = 2 * prediction_length @@ -318,13 +319,13 @@ def plot_predictions( plot_title = f"Example {indices[i]}" elif using_pipeline: - ts_y_hat = create_timestamps(predictions_df[timestamp_column].iloc[0], freq=freq, periods=prediction_length) - y_hat = predictions_df[f"{channel}_prediction"] + ts_y_hat = create_timestamps(exploded_predictions_df[timestamp_column].iloc[0], freq=freq, periods=prediction_length) + y_hat = exploded_predictions_df[f"{channel}_prediction"] # get context # ts_y = create_timestamps(context_df[timestamp_column].iloc[0], freq=freq, periods=len(context_df)) - ts_y = context_df[timestamp_column].values - y = context_df[channel].values + ts_y = input_df[timestamp_column].values + y = input_df[channel].values border = None plot_title = f"Forecast for {channel}" From 7a58c051bf54f0c89bcce46ec6d39b1805806988 Mon Sep 17 00:00:00 2001 From: Fayvor Love Date: Fri, 30 Aug 2024 15:31:58 -0400 Subject: [PATCH 3/3] Run make style for formatting. Signed-off-by: Fayvor Love --- .../demand_forecast_zeroshot_recipe_minimal.ipynb | 13 +++++++++++-- tsfm_public/toolkit/visualization.py | 10 +++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb b/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb index 0cd46783..dd1bf605 100644 --- a/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb +++ b/notebooks/recipes/energy_demand_forecasting/demand_forecast_zeroshot_recipe_minimal.ipynb @@ -33,7 +33,9 @@ ], "source": [ "import pathlib\n", + "\n", "import pandas as pd\n", + "\n", "from tsfm_public import TimeSeriesForecastingPipeline, TinyTimeMixerForPrediction\n", "from tsfm_public.toolkit.visualization import plot_predictions" ] @@ -57,6 +59,8 @@ ], "source": [ "import tsfm_public\n", + "\n", + "\n", "tsfm_public.__version__" ] }, @@ -490,7 +494,12 @@ ], "source": [ "pipeline = TimeSeriesForecastingPipeline(\n", - " zeroshot_model, timestamp_column=timestamp_column, target_columns=[target_column], explode_forecasts=True, freq=\"h\", id_columns=[]\n", + " zeroshot_model,\n", + " timestamp_column=timestamp_column,\n", + " target_columns=[target_column],\n", + " explode_forecasts=True,\n", + " freq=\"h\",\n", + " id_columns=[],\n", ")\n", "zeroshot_forecast = pipeline(data)\n", "zeroshot_forecast.head()" @@ -529,7 +538,7 @@ " timestamp_column=timestamp_column,\n", " channel=target_column,\n", " indices=[-1],\n", - " num_plots=1\n", + " num_plots=1,\n", ")" ] }, diff --git a/tsfm_public/toolkit/visualization.py b/tsfm_public/toolkit/visualization.py index d47ea0ee..eb52d6a4 100644 --- a/tsfm_public/toolkit/visualization.py +++ b/tsfm_public/toolkit/visualization.py @@ -286,7 +286,9 @@ def plot_predictions( using_pipeline = False plot_test_data = True else: - raise RuntimeError("You must provide either input_df and predictions_df, or dset and model, or input_df and exploded_predictions_df.") + raise RuntimeError( + "You must provide either input_df and predictions_df, or dset and model, or input_df and exploded_predictions_df." + ) if plot_context is None: plot_context = 2 * prediction_length @@ -317,9 +319,11 @@ def plot_predictions( y = y.values border = ts_y[-prediction_length] plot_title = f"Example {indices[i]}" - + elif using_pipeline: - ts_y_hat = create_timestamps(exploded_predictions_df[timestamp_column].iloc[0], freq=freq, periods=prediction_length) + ts_y_hat = create_timestamps( + exploded_predictions_df[timestamp_column].iloc[0], freq=freq, periods=prediction_length + ) y_hat = exploded_predictions_df[f"{channel}_prediction"] # get context