From d5b1d3eed3fabf9603d2ca553d934d89ab8c203a Mon Sep 17 00:00:00 2001 From: Sampurna Goswami Date: Mon, 28 Oct 2024 17:53:15 +0530 Subject: [PATCH 1/6] Initial commit with the codes --- ...casting-function-gap-batch-inference.ipynb | 941 ++++++++++++++++++ ...casting-function-gap-local-inference.ipynb | 491 +++++++++ ...ml-forecasting-function-gap-training.ipynb | 620 ++++++++++++ .../forecasting_script/forecasting_script.py | 64 ++ .../parallel_run_step.settings.json | 1 + .../helper.py | 118 +++ .../images/forecast_function_at_train.png | Bin 0 -> 70504 bytes .../forecast_function_away_from_train.png | Bin 0 -> 66720 bytes 8 files changed, 2235 insertions(+) create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/parallel_run_step.settings.json create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/images/forecast_function_at_train.png create mode 100644 sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/images/forecast_function_away_from_train.png diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb new file mode 100644 index 00000000000..955a89fe4ad --- /dev/null +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb @@ -0,0 +1,941 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "This notebook demonstrates the full interface of the `forecast()` function. \n", + "\n", + "The best known and most frequent usage of `forecast` enables forecasting on test sets that immediately follows training data. \n", + "\n", + "However, in many use cases it is necessary to continue using the model for some time before retraining it. This happens especially in **high frequency forecasting** when forecasts need to be made more frequently than the model can be retrained. Examples are in Internet of Things and predictive cloud resource scaling.\n", + "\n", + "Here we show how to use the `forecast()` function when a time gap exists between training data and prediction period.\n", + "\n", + "Terminology:\n", + "* forecast origin: the last period when the target value is known\n", + "* forecast periods(s): the period(s) for which the value of the target is desired.\n", + "* lookback: how many past periods (before forecast origin) the model function depends on. The larger of number of lags and length of rolling window.\n", + "* prediction context: `lookback` periods immediately preceding the forecast origin" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. **Model** \n", + " We will need the MLflow model, which is downloaded at the end of the training notebook. Follow any training notebook to get the model. The MLflow model is usually downloaded to the folder: `./artifact_downloads/outputs/mlflow-model`.\n", + "\n", + "2. **Environment** \n", + " We will need the environment to load the model. Please run the following commands to create the environment (the conda file is usually downloaded to: `./artifact_downloads/outputs/mlflow-model/conda.yaml`):\n", + " - `conda env create --file `\n", + " - `conda activate project_environment`\n", + "\n", + "3. **Register environment as kernel** \n", + " - Please run the following command to register the environment as a kernel: \n", + " ```bash\n", + " python -m ipykernel install --user --name project_environment --display-name \"model-inference\"\n", + " ```\n", + " - Refresh the kernel and then select the newly created kernel named `model-inference` from the kernel dropdown.\n", + " \n", + " Now we are good to run this notebook in the newly created kernel.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "TIME_COLUMN_NAME = \"date\"\n", + "TIME_SERIES_ID_COLUMN_NAME = \"time_series_id\"\n", + "TARGET_COLUMN_NAME = \"y\"\n", + "lags = [1, 2, 3]\n", + "forecast_horizon = 6" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "import mlflow.pyfunc\n", + "import mlflow.sklearn\n", + "import pandas as pd\n", + "\n", + "df_train = pd.read_parquet('./data/training-mltable-folder/df_train.parquet') # We stored the training and test data during training\n", + "df_test = pd.read_parquet('./data/testing-mltable-folder/df_test.parquet')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Batch Inferencing" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "azureml://eastus.api.azureml.ms/mlflow/v1.0/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourceGroups/aml-benchmarking/providers/Microsoft.MachineLearningServices/workspaces/aml-benchmarking-rd\n" + ] + } + ], + "source": [ + "import mlflow\n", + "# Import required libraries\n", + "from azure.identity import DefaultAzureCredential\n", + "from azure.ai.ml import MLClient\n", + "credential = DefaultAzureCredential()\n", + "ml_client = None\n", + "\n", + "subscription_id = \"72c03bf3-4e69-41af-9532-dfcdc3eefef4\"#\"\"\n", + "resource_group = \"aml-benchmarking\"#\"\"\n", + "workspace = \"aml-benchmarking-rd\"#\"\"\n", + "\n", + "ml_client = MLClient(credential, subscription_id, resource_group, workspace)\n", + "\n", + "# Obtain the tracking URL from MLClient\n", + "MLFLOW_TRACKING_URI = ml_client.workspaces.get(\n", + " name=ml_client.workspace_name\n", + ").mlflow_tracking_uri\n", + "\n", + "print(MLFLOW_TRACKING_URI)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Current tracking uri: azureml://eastus.api.azureml.ms/mlflow/v1.0/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourceGroups/aml-benchmarking/providers/Microsoft.MachineLearningServices/workspaces/aml-benchmarking-rd\n" + ] + } + ], + "source": [ + "# Set the MLFLOW TRACKING URI\n", + "\n", + "mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)\n", + "print(\"\\nCurrent tracking uri: {}\".format(mlflow.get_tracking_uri()))\n", + "\n", + "from mlflow.tracking.client import MlflowClient\n", + "from mlflow.artifacts import download_artifacts\n", + "\n", + "# Initialize MLFlow client\n", + "mlflow_client = MlflowClient()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Parent Run: \n", + ", info=, inputs=>\n" + ] + } + ], + "source": [ + "# job_name = returned_job.name\n", + "# Example if providing an specific Job name/ID\n", + "job_name = \"yellow_camera_1n84g0vcwp\"\n", + "\n", + "# Get the parent run\n", + "mlflow_parent_run = mlflow_client.get_run(job_name)\n", + "\n", + "print(\"Parent Run: \")\n", + "print(mlflow_parent_run)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found best child run id: yellow_camera_1n84g0vcwp_4\n", + "Best child run: \n", + ", info=, inputs=>\n" + ] + } + ], + "source": [ + "# Get the best model's child run\n", + "best_child_run_id = mlflow_parent_run.data.tags[\"automl_best_child_run_id\"]\n", + "print(\"Found best child run id: \", best_child_run_id)\n", + "\n", + "best_run = mlflow_client.get_run(best_child_run_id)\n", + "\n", + "print(\"Best child run: \")\n", + "print(best_run)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "from azure.ai.ml.entities import (\n", + " Environment,\n", + " BatchEndpoint,\n", + " BatchDeployment,\n", + " BatchRetrySettings,\n", + " Model,\n", + ")\n", + "from azure.ai.ml.constants import BatchDeploymentOutputAction\n", + "\n", + "model_name = \"test-gap-batch-endpoint\"\n", + "batch_endpoint_name = \"gap-batch-\" + datetime.datetime.now().strftime(\n", + " \"%m%d%H%M%f\"\n", + ")\n", + "\n", + "model = Model(\n", + " path=f\"azureml://jobs/{best_run.info.run_id}/outputs/artifacts/outputs/model.pkl\",\n", + " name=model_name,\n", + " description=\"Gap prediction sample best model\",\n", + ")\n", + "registered_model = ml_client.models.create_or_update(model)\n", + "\n", + "env = Environment(\n", + " name=\"automl-tabular-env\",\n", + " description=\"environment for automl inference\",\n", + " image=\"mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04:latest\",\n", + " conda_file=\"artifact_downloads/outputs/conda_env_v_1_0_0.yml\",\n", + ")\n", + "\n", + "endpoint = BatchEndpoint(\n", + " name=batch_endpoint_name,\n", + " description=\"this is a sample batch endpoint\",\n", + ")\n", + "ml_client.begin_create_or_update(endpoint).wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.core.exceptions import ResourceNotFoundError\n", + "from azure.ai.ml.entities import AmlCompute\n", + "\n", + "cluster_name = \"gap-cluster\"\n", + "\n", + "try:\n", + " # Retrieve an already attached Azure Machine Learning Compute.\n", + " compute = ml_client.compute.get(cluster_name)\n", + "except ResourceNotFoundError as e:\n", + " compute = AmlCompute(\n", + " name=cluster_name,\n", + " size=\"STANDARD_DS12_V2\",\n", + " type=\"amlcompute\",\n", + " min_instances=0,\n", + " max_instances=4,\n", + " idle_time_before_scale_down=120,\n", + " )\n", + " poller = ml_client.begin_create_or_update(compute)\n", + " poller.wait()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gap-batch-10241739762384'" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "batch_endpoint_name" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "output_file = \"forecast.csv\"\n", + "batch_deployment = BatchDeployment(\n", + " name=\"oj-non-mlflow-deployment\",\n", + " description=\"this is a sample non-mlflow deployment\",\n", + " endpoint_name=batch_endpoint_name,\n", + " model=registered_model,\n", + " code_path=\"./forecasting_script\",\n", + " scoring_script=\"forecasting_script.py\",\n", + " environment=env,\n", + " environment_variables={\n", + " \"TARGET_COLUMN_NAME\": TARGET_COLUMN_NAME,\n", + " },\n", + " compute=cluster_name,\n", + " instance_count=1, #2\n", + " max_concurrency_per_instance=1, #2\n", + " mini_batch_size=1, #10\n", + " output_action=BatchDeploymentOutputAction.APPEND_ROW,\n", + " output_file_name=output_file,\n", + " retry_settings=BatchRetrySettings(max_retries=3, timeout=30),\n", + " logging_level=\"info\",\n", + " properties={\"include_output_header\": \"true\"},\n", + " tags={\"include_output_header\": \"true\"},\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "ml_client.begin_create_or_update(batch_deployment).wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml import Input\n", + "from azure.ai.ml.constants import AssetTypes\n", + "my_test_data_input = Input(\n", + " type=AssetTypes.URI_FOLDER,\n", + " path=\"./data/testing-mltable-folder\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "MLClient(credential=,\n", + " subscription_id=72c03bf3-4e69-41af-9532-dfcdc3eefef4,\n", + " resource_group_name=aml-benchmarking,\n", + " workspace_name=aml-benchmarking-rd)" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ml_client" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gap-batch-10241739762384'" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "batch_endpoint_name" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'type': 'uri_folder', 'path': './data/testing-mltable-folder'}" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "my_test_data_input" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "job = ml_client.batch_endpoints.invoke(\n", + " endpoint_name=batch_endpoint_name,\n", + " input=my_test_data_input,\n", + " deployment_name=\"oj-non-mlflow-deployment\", # name is required as default deployment is not set\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running\n", + "RunId: batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be\n", + "Web View: https://ml.azure.com/runs/batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", + "\n", + "Streaming logs/azureml/executionlogs.txt\n", + "========================================\n", + "\n", + "[2024-10-24 12:12:27Z] Submitting 1 runs, first five are: 16057eb9:658e18fb-67ae-4a36-bbdf-fd8db589f027\n", + "[2024-10-24 12:20:40Z] Execution of experiment failed, update experiment status and cancel running nodes.\n", + "\n", + "Execution Summary\n", + "=================\n", + "RunId: batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be\n", + "Web View: https://ml.azure.com/runs/batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n" + ] + }, + { + "ename": "JobException", + "evalue": "Exception : \n {\n \"error\": {\n \"code\": \"UserError\",\n \"message\": \"Pipeline has failed child jobs. For more details and logs, please go to the job detail page and check the child jobs.\",\n \"message_format\": \"Pipeline has failed child jobs. {0}\",\n \"message_parameters\": {},\n \"reference_code\": \"PipelineHasStepJobFailed\",\n \"details\": []\n },\n \"environment\": \"eastus\",\n \"location\": \"eastus\",\n \"time\": \"2024-10-24T12:20:40.0429Z\",\n \"component_name\": \"\"\n} ", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mJobException\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[49], line 5\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28mprint\u001b[39m(batch_job\u001b[38;5;241m.\u001b[39mstatus)\n\u001b[0;32m 4\u001b[0m \u001b[38;5;66;03m# stream the job logs\u001b[39;00m\n\u001b[1;32m----> 5\u001b[0m \u001b[43mml_client\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mjobs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mjob_name\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\core\\tracing\\decorator.py:94\u001b[0m, in \u001b[0;36mdistributed_trace..decorator..wrapper_use_tracer\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 92\u001b[0m span_impl_type \u001b[38;5;241m=\u001b[39m settings\u001b[38;5;241m.\u001b[39mtracing_implementation()\n\u001b[0;32m 93\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m span_impl_type \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m---> 94\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m func(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 96\u001b[0m \u001b[38;5;66;03m# Merge span is parameter is set, but only if no explicit parent are passed\u001b[39;00m\n\u001b[0;32m 97\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m merge_span \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m passed_in_parent:\n", + "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\ai\\ml\\_telemetry\\activity.py:289\u001b[0m, in \u001b[0;36mmonitor_with_activity..monitor..wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 285\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m tracer\u001b[38;5;241m.\u001b[39mspan():\n\u001b[0;32m 286\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m log_activity(\n\u001b[0;32m 287\u001b[0m logger\u001b[38;5;241m.\u001b[39mpackage_logger, activity_name \u001b[38;5;129;01mor\u001b[39;00m f\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m, activity_type, custom_dimensions\n\u001b[0;32m 288\u001b[0m ):\n\u001b[1;32m--> 289\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m f(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 290\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(logger, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpackage_logger\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m 291\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m log_activity(logger\u001b[38;5;241m.\u001b[39mpackage_logger, activity_name \u001b[38;5;129;01mor\u001b[39;00m f\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m, activity_type, custom_dimensions):\n", + "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\ai\\ml\\operations\\_job_operations.py:818\u001b[0m, in \u001b[0;36mJobOperations.stream\u001b[1;34m(self, name)\u001b[0m\n\u001b[0;32m 815\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _is_pipeline_child_job(job_object):\n\u001b[0;32m 816\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m PipelineChildJobError(job_id\u001b[38;5;241m=\u001b[39mjob_object\u001b[38;5;241m.\u001b[39mid)\n\u001b[1;32m--> 818\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_stream_logs_until_completion\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 819\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_runs_operations\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjob_object\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_datastore_operations\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrequests_pipeline\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_requests_pipeline\u001b[49m\n\u001b[0;32m 820\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\ai\\ml\\operations\\_job_ops_helper.py:334\u001b[0m, in \u001b[0;36mstream_logs_until_completion\u001b[1;34m(run_operations, job_resource, datastore_operations, raise_exception_on_failed_job, requests_pipeline)\u001b[0m\n\u001b[0;32m 332\u001b[0m file_handle\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 333\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m--> 334\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m JobException(\n\u001b[0;32m 335\u001b[0m message\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mException : \u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(json\u001b[38;5;241m.\u001b[39mdumps(error, indent\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m4\u001b[39m)),\n\u001b[0;32m 336\u001b[0m target\u001b[38;5;241m=\u001b[39mErrorTarget\u001b[38;5;241m.\u001b[39mJOB,\n\u001b[0;32m 337\u001b[0m no_personal_data_message\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mException raised on failed job.\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 338\u001b[0m error_category\u001b[38;5;241m=\u001b[39mErrorCategory\u001b[38;5;241m.\u001b[39mSYSTEM_ERROR,\n\u001b[0;32m 339\u001b[0m )\n\u001b[0;32m 341\u001b[0m file_handle\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 342\u001b[0m file_handle\u001b[38;5;241m.\u001b[39mflush()\n", + "\u001b[1;31mJobException\u001b[0m: Exception : \n {\n \"error\": {\n \"code\": \"UserError\",\n \"message\": \"Pipeline has failed child jobs. For more details and logs, please go to the job detail page and check the child jobs.\",\n \"message_format\": \"Pipeline has failed child jobs. {0}\",\n \"message_parameters\": {},\n \"reference_code\": \"PipelineHasStepJobFailed\",\n \"details\": []\n },\n \"environment\": \"eastus\",\n \"location\": \"eastus\",\n \"time\": \"2024-10-24T12:20:40.0429Z\",\n \"component_name\": \"\"\n} " + ] + } + ], + "source": [ + "job_name = job.name\n", + "batch_job = ml_client.jobs.get(name=job_name)\n", + "print(batch_job.status)\n", + "# stream the job logs\n", + "ml_client.jobs.stream(name=job_name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Local inferencing from model pickle\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'azureml.training'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[3], line 5\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mmlflow\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01msklearn\u001b[39;00m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[1;32m----> 5\u001b[0m fitted_model \u001b[38;5;241m=\u001b[39m \u001b[43mmlflow\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msklearn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mload_model\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmlflow_dir\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\mlflow\\sklearn\\__init__.py:638\u001b[0m, in \u001b[0;36mload_model\u001b[1;34m(model_uri, dst_path)\u001b[0m\n\u001b[0;32m 636\u001b[0m sklearn_model_artifacts_path \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(local_model_path, flavor_conf[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpickled_model\u001b[39m\u001b[38;5;124m\"\u001b[39m])\n\u001b[0;32m 637\u001b[0m serialization_format \u001b[38;5;241m=\u001b[39m flavor_conf\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mserialization_format\u001b[39m\u001b[38;5;124m\"\u001b[39m, SERIALIZATION_FORMAT_PICKLE)\n\u001b[1;32m--> 638\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_load_model_from_local_file\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 639\u001b[0m \u001b[43m \u001b[49m\u001b[43mpath\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msklearn_model_artifacts_path\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mserialization_format\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mserialization_format\u001b[49m\n\u001b[0;32m 640\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\mlflow\\sklearn\\__init__.py:453\u001b[0m, in \u001b[0;36m_load_model_from_local_file\u001b[1;34m(path, serialization_format)\u001b[0m\n\u001b[0;32m 449\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(path, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrb\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[0;32m 450\u001b[0m \u001b[38;5;66;03m# Models serialized with Cloudpickle cannot necessarily be deserialized using Pickle;\u001b[39;00m\n\u001b[0;32m 451\u001b[0m \u001b[38;5;66;03m# That's why we check the serialization format of the model before deserializing\u001b[39;00m\n\u001b[0;32m 452\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m serialization_format \u001b[38;5;241m==\u001b[39m SERIALIZATION_FORMAT_PICKLE:\n\u001b[1;32m--> 453\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mpickle\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mload\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 454\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m serialization_format \u001b[38;5;241m==\u001b[39m SERIALIZATION_FORMAT_CLOUDPICKLE:\n\u001b[0;32m 455\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mcloudpickle\u001b[39;00m\n", + "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'azureml.training'" + ] + } + ], + "source": [ + "import mlflow.pyfunc\n", + "import mlflow.sklearn\n", + "# Please ensure that the training artifacts are downloaded. For more details refer to the training notebook\n", + "mlflow_dir = \"./artifact_downloads/outputs/mlflow-model\"\n", + "fitted_model = mlflow.sklearn.load_model(mlflow_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df_test[df_test['time_series_id']==\"ts1\"].head(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting from the trained model\n", + "\n", + "In this section we will review the forecast interface for two main scenarios: forecasting right after the training data, and the more complex interface for forecasting when there is a gap (in the time sense) between training and testing data.\n", + "\n", + "## X_train is directly followed by the X_test\n", + "Let's first consider the case when the prediction period immediately follows the training data. This is typical in scenarios where we have the time to retrain the model every time we wish to forecast. Forecasts that are made on daily and slower cadence typically fall into this category. Retraining the model every time benefits the accuracy because the most recent data is often the most informative.\n", + "\n", + "\n", + "\"Description\"\n", + "\n", + "We use X_test as a forecast request to generate the predictions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_test = df_test.copy()\n", + "y_test = X_test.pop(TARGET_COLUMN_NAME).values.astype(float)\n", + "\n", + "y_pred_no_gap, xy_nogap = fitted_model.forecast(X_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "### Confidence Intervals\n", + "Forecasting model may be used for the prediction of forecasting intervals by running forecast_quantiles(). This method accepts the same parameters as forecast()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "quantiles = fitted_model.forecast_quantiles(X_test)\n", + "quantiles" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "### Distribution forecasts\n", + "Often the figure of interest is not just the point prediction, but the prediction at some quantile of the distribution. This arises when the forecast is used to control some kind of inventory, for example of grocery items or virtual machines for a cloud service. In such case, the control point is usually something like \"we want the item to be in stock and not run out 99% of the time\". This is called a \"service level\". Here is how you get quantile forecasts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Specify which quantiles you would like\n", + "fitted_model.quantiles = [0.01, 0.5, 0.95]\n", + "\n", + "# use forecast_quantiles function, not the forecast() one\n", + "y_pred_quantiles = fitted_model.forecast_quantiles(X_test)\n", + "\n", + "# quantile forecasts returned in a Dataframe along with the time and time series id columns\n", + "y_pred_quantiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting away from training data\n", + "Suppose we trained a model, some time passed, and now we want to apply the model without re-training. If the model \"looks back\" -- uses previous values of the target -- then we somehow need to provide those values to the model.\n", + "\n", + "\"Description\"\n", + "\n", + "The notion of forecast origin comes into play: **the forecast origin is the last period for which we have seen the target value.** This applies per time-series, so each time-series can have a different forecast origin.\n", + "\n", + "The part of data before the forecast origin is the **prediction context**. To provide the context values the model needs when it looks back, we pass definite values in y_test (aligned with corresponding times in X_test)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", + "from helper import get_timeseries, make_forecasting_query\n", + "X_context, y_context, X_away, y_away = get_timeseries(\n", + " train_len=42, # train data was 30 steps long\n", + " test_len=4,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=2,\n", + ")\n", + "\n", + "print(\"End of the data we trained on:\")\n", + "print(df_train.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())\n", + "\n", + "print(\"\\nStart of the data we want to predict on:\")\n", + "print(X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a gap of 12 hours between end of training and beginning of X_away. (It looks like 13 because all timestamps point to the start of the one hour periods.) Using only X_away will fail without adding context data for the model to consume" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "try:\n", + " y_pred_away, xy_away = fitted_model.forecast(X_away)\n", + " xy_away\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of y (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define y value to establish the forecast origin.\n", + "\n", + "We will use the helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see where the context data ends - it ends, by construction, just before the testing data starts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "print(\n", + " X_context.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", + " [\"min\", \"max\", \"count\"]\n", + " )\n", + ")\n", + "print(\n", + " X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", + " [\"min\", \"max\", \"count\"]\n", + " )\n", + ")\n", + "X_context.tail(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of y (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define y value to establish the forecast origin.\n", + "\n", + "We will use this helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Since the length of the lookback is 3, we need to add 3 periods from the context to the request so that the model has the data it needs\n", + "\n", + "# Put the X and y back together for a while. They like each other and it makes them happy.\n", + "X_context[TARGET_COLUMN_NAME] = y_context\n", + "X_away[TARGET_COLUMN_NAME] = y_away\n", + "fulldata = pd.concat([X_context, X_away])\n", + "\n", + "# Forecast origin is the last point of data, which is one 1-hr period before test\n", + "forecast_origin = X_away[TIME_COLUMN_NAME].min() - pd.DateOffset(hours=1)\n", + "# it is indeed the last point of the context\n", + "assert forecast_origin == X_context[TIME_COLUMN_NAME].max()\n", + "print(\"Forecast origin: \" + str(forecast_origin))\n", + "\n", + "# The model uses lags and rolling windows to look back in time\n", + "n_lookback_periods = max(lags) # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", + "lookback = pd.DateOffset(hours=n_lookback_periods)\n", + "horizon = pd.DateOffset(hours=forecast_horizon)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# now make the forecast query from context (refer to figure)\n", + "X_pred, y_pred = make_forecasting_query(\n", + " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", + ")\n", + "\n", + "# show the forecast request aligned\n", + "X_show = X_pred.copy()\n", + "X_show[TARGET_COLUMN_NAME] = y_pred\n", + "X_show[X_show['time_series_id']==\"ts0\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_pred['data_type']=\"unknown\" # Our trining had an additional column called data_type, hence, adding it" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Now everything should work\n", + "y_pred_away, xy_away = fitted_model.forecast(X_pred, y_pred)\n", + "\n", + "# show the forecast aligned without the generated features\n", + "X_show = xy_away.reset_index()\n", + "X_show[[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]] # prediction is in _automl_target_col" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "### Let us look at the tail of training data and the head of the test data for one grain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If there is a gap between the train and the test data, and the test data uses lags/ rolling forecasts, we need to append the context data such that the test data has access to the lags\n", + "In the above case, train_data ends at 2000-01-02 05:00:00" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_show[X_show['time_series_id'] == \"ts1\"][[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Forecasting using batch endpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "category": "tutorial", + "compute": [ + "Remote" + ], + "datasets": [ + "None" + ], + "deployment": [ + "None" + ], + "exclude_from_index": false, + "framework": [ + "Azure ML AutoML" + ], + "friendly_name": "Forecasting away from training data", + "index_order": 3, + "kernelspec": { + "display_name": "sdkv2-test1", + "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.9.20" + }, + "microsoft": { + "ms_spell_check": { + "ms_spell_check_language": "en" + } + }, + "nteract": { + "version": "nteract-front-end@1.0.0" + }, + "tags": [ + "Forecasting", + "Confidence Intervals" + ], + "task": "Forecasting" + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb new file mode 100644 index 00000000000..6e30abd5d5a --- /dev/null +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb @@ -0,0 +1,491 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "This notebook demonstrates the full interface of the `forecast()` function. \n", + "\n", + "The best known and most frequent usage of `forecast` enables forecasting on test sets that immediately follows training data. \n", + "\n", + "However, in many use cases it is necessary to continue using the model for some time before retraining it. This happens especially in **high frequency forecasting** when forecasts need to be made more frequently than the model can be retrained. Examples are in Internet of Things and predictive cloud resource scaling.\n", + "\n", + "Here we show how to use the `forecast()` function when a time gap exists between training data and prediction period.\n", + "\n", + "Terminology:\n", + "* forecast origin: the last period when the target value is known\n", + "* forecast periods(s): the period(s) for which the value of the target is desired.\n", + "* lookback: how many past periods (before forecast origin) the model function depends on. The larger of number of lags and length of rolling window.\n", + "* prediction context: `lookback` periods immediately preceding the forecast origin" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. **Model** \n", + " We will need the MLflow model, which is downloaded at the end of the training notebook. Follow any training notebook to get the model. The MLflow model is usually downloaded to the folder: `./artifact_downloads/outputs/mlflow-model`.\n", + "\n", + "2. **Environment** \n", + " We will need the environment to load the model. Please run the following commands to create the environment (the conda file is usually downloaded to: `./artifact_downloads/outputs/mlflow-model/conda.yaml`):\n", + " - `conda env create --file `\n", + " - `conda activate project_environment`\n", + "\n", + "3. **Register environment as kernel** \n", + " - Please run the following command to register the environment as a kernel: \n", + " ```bash\n", + " python -m ipykernel install --user --name project_environment --display-name \"model-inference\"\n", + " ```\n", + " - Refresh the kernel and then select the newly created kernel named `model-inference` from the kernel dropdown.\n", + " \n", + " Now we are good to run this notebook in the newly created kernel.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "TIME_COLUMN_NAME = \"date\"\n", + "TIME_SERIES_ID_COLUMN_NAME = \"time_series_id\"\n", + "TARGET_COLUMN_NAME = \"y\"\n", + "lags = [1, 2, 3]\n", + "forecast_horizon = 6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Local inferencing from model pickle\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Please ensure that the training artifacts are downloaded. For more details refer to the training notebook\n", + "mlflow_dir = \"./artifact_downloads/outputs/mlflow-model\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import mlflow.pyfunc\n", + "import mlflow.sklearn\n", + "import pandas as pd\n", + "\n", + "fitted_model = mlflow.sklearn.load_model(mlflow_dir)\n", + "df_train = pd.read_parquet('./data/training-mltable-folder/df_train.parquet') # We stored the training and test data during training\n", + "df_test = pd.read_parquet('./data/testing-mltable-folder/df_test.parquet')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df_test[df_test['time_series_id']==\"ts1\"].head(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting from the trained model\n", + "\n", + "In this section we will review the forecast interface for two main scenarios: forecasting right after the training data, and the more complex interface for forecasting when there is a gap (in the time sense) between training and testing data.\n", + "\n", + "## X_train is directly followed by the X_test\n", + "Let's first consider the case when the prediction period immediately follows the training data. This is typical in scenarios where we have the time to retrain the model every time we wish to forecast. Forecasts that are made on daily and slower cadence typically fall into this category. Retraining the model every time benefits the accuracy because the most recent data is often the most informative.\n", + "\n", + "\n", + "\"Description\"\n", + "\n", + "We use X_test as a forecast request to generate the predictions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_test = df_test.copy()\n", + "y_test = X_test.pop(TARGET_COLUMN_NAME).values.astype(float)\n", + "\n", + "y_pred_no_gap, xy_nogap = fitted_model.forecast(X_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "### Confidence Intervals\n", + "Forecasting model may be used for the prediction of forecasting intervals by running forecast_quantiles(). This method accepts the same parameters as forecast()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "quantiles = fitted_model.forecast_quantiles(X_test)\n", + "quantiles" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "### Distribution forecasts\n", + "Often the figure of interest is not just the point prediction, but the prediction at some quantile of the distribution. This arises when the forecast is used to control some kind of inventory, for example of grocery items or virtual machines for a cloud service. In such case, the control point is usually something like \"we want the item to be in stock and not run out 99% of the time\". This is called a \"service level\". Here is how you get quantile forecasts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Specify which quantiles you would like\n", + "fitted_model.quantiles = [0.01, 0.5, 0.95]\n", + "\n", + "# use forecast_quantiles function, not the forecast() one\n", + "y_pred_quantiles = fitted_model.forecast_quantiles(X_test)\n", + "\n", + "# quantile forecasts returned in a Dataframe along with the time and time series id columns\n", + "y_pred_quantiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting away from training data\n", + "Suppose we trained a model, some time passed, and now we want to apply the model without re-training. If the model \"looks back\" -- uses previous values of the target -- then we somehow need to provide those values to the model.\n", + "\n", + "\"Description\"\n", + "\n", + "The notion of forecast origin comes into play: **the forecast origin is the last period for which we have seen the target value.** This applies per time-series, so each time-series can have a different forecast origin.\n", + "\n", + "The part of data before the forecast origin is the **prediction context**. To provide the context values the model needs when it looks back, we pass definite values in y_test (aligned with corresponding times in X_test)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", + "from helper import get_timeseries, make_forecasting_query\n", + "X_context, y_context, X_away, y_away = get_timeseries(\n", + " train_len=42, # train data was 30 steps long\n", + " test_len=4,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=2,\n", + ")\n", + "\n", + "print(\"End of the data we trained on:\")\n", + "print(df_train.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())\n", + "\n", + "print(\"\\nStart of the data we want to predict on:\")\n", + "print(X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a gap of 12 hours between end of training and beginning of X_away. (It looks like 13 because all timestamps point to the start of the one hour periods.) Using only X_away will fail without adding context data for the model to consume" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "try:\n", + " y_pred_away, xy_away = fitted_model.forecast(X_away)\n", + " xy_away\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of y (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define y value to establish the forecast origin.\n", + "\n", + "We will use the helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see where the context data ends - it ends, by construction, just before the testing data starts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "print(\n", + " X_context.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", + " [\"min\", \"max\", \"count\"]\n", + " )\n", + ")\n", + "print(\n", + " X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", + " [\"min\", \"max\", \"count\"]\n", + " )\n", + ")\n", + "X_context.tail(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of y (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define y value to establish the forecast origin.\n", + "\n", + "We will use this helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Since the length of the lookback is 3, we need to add 3 periods from the context to the request so that the model has the data it needs\n", + "\n", + "# Put the X and y back together for a while. They like each other and it makes them happy.\n", + "X_context[TARGET_COLUMN_NAME] = y_context\n", + "X_away[TARGET_COLUMN_NAME] = y_away\n", + "fulldata = pd.concat([X_context, X_away])\n", + "\n", + "# Forecast origin is the last point of data, which is one 1-hr period before test\n", + "forecast_origin = X_away[TIME_COLUMN_NAME].min() - pd.DateOffset(hours=1)\n", + "# it is indeed the last point of the context\n", + "assert forecast_origin == X_context[TIME_COLUMN_NAME].max()\n", + "print(\"Forecast origin: \" + str(forecast_origin))\n", + "\n", + "# The model uses lags and rolling windows to look back in time\n", + "n_lookback_periods = max(lags) # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", + "lookback = pd.DateOffset(hours=n_lookback_periods)\n", + "horizon = pd.DateOffset(hours=forecast_horizon)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# now make the forecast query from context (refer to figure)\n", + "X_pred, y_pred = make_forecasting_query(\n", + " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", + ")\n", + "\n", + "# show the forecast request aligned\n", + "X_show = X_pred.copy()\n", + "X_show[TARGET_COLUMN_NAME] = y_pred\n", + "X_show[X_show['time_series_id']==\"ts0\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_pred['data_type']=\"unknown\" # Our trining had an additional column called data_type, hence, adding it" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Now everything should work\n", + "y_pred_away, xy_away = fitted_model.forecast(X_pred, y_pred)\n", + "\n", + "# show the forecast aligned without the generated features\n", + "X_show = xy_away.reset_index()\n", + "X_show[[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]] # prediction is in _automl_target_col" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "### Let us look at the tail of training data and the head of the test data for one grain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If there is a gap between the train and the test data, and the test data uses lags/ rolling forecasts, we need to append the context data such that the test data has access to the lags\n", + "In the above case, train_data ends at 2000-01-02 05:00:00" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "X_show[X_show['time_series_id'] == \"ts1\"][[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "category": "tutorial", + "compute": [ + "Remote" + ], + "datasets": [ + "None" + ], + "deployment": [ + "None" + ], + "exclude_from_index": false, + "framework": [ + "Azure ML AutoML" + ], + "friendly_name": "Forecasting away from training data", + "index_order": 3, + "kernelspec": { + "display_name": "model-inference", + "language": "python", + "name": "project_environment" + }, + "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.9.19" + }, + "microsoft": { + "ms_spell_check": { + "ms_spell_check_language": "en" + } + }, + "nteract": { + "version": "nteract-front-end@1.0.0" + }, + "tags": [ + "Forecasting", + "Confidence Intervals" + ], + "task": "Forecasting", + "vscode": { + "interpreter": { + "hash": "6bd77c88278e012ef31757c15997a7bea8c943977c43d6909403c00ae11d43ca" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb new file mode 100644 index 00000000000..833c34bf868 --- /dev/null +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb @@ -0,0 +1,620 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "This notebook demonstrates the full interface of the `forecast()` function. \n", + "\n", + "The best known and most frequent usage of `forecast` enables forecasting on test sets that immediately follows training data. \n", + "\n", + "However, in many use cases it is necessary to continue using the model for some time before retraining it. This happens especially in **high frequency forecasting** when forecasts need to be made more frequently than the model can be retrained. Examples are in Internet of Things and predictive cloud resource scaling.\n", + "\n", + "Here we show how to use the `forecast()` function when a time gap exists between training data and prediction period.\n", + "\n", + "Terminology:\n", + "* forecast origin: the last period when the target value is known\n", + "* forecast periods(s): the period(s) for which the value of the target is desired.\n", + "* lookback: how many past periods (before forecast origin) the model function depends on. The larger of number of lags and length of rolling window.\n", + "* prediction context: `lookback` periods immediately preceding the forecast origin" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please make sure you have followed the [configuration notebook](https://github.com/Azure/MachineLearningNotebooks/blob/master/configuration.ipynb) so that your ML workspace information is saved in the config file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import pandas as pd\n", + "import numpy as np\n", + "import logging\n", + "import warnings\n", + "\n", + "# Import required libraries\n", + "from azure.identity import DefaultAzureCredential\n", + "from azure.ai.ml import MLClient\n", + "\n", + "from azure.ai.ml.constants import AssetTypes, InputOutputModes\n", + "from azure.ai.ml import automl\n", + "from azure.ai.ml import Input\n", + "\n", + "# Squash warning messages for cleaner output in the notebook\n", + "warnings.showwarning = lambda *args, **kwargs: None\n", + "\n", + "np.set_printoptions(precision=4, suppress=True, linewidth=120)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "credential = DefaultAzureCredential()\n", + "ml_client = None\n", + "try:\n", + " ml_client = MLClient.from_config(credential)\n", + "except Exception as ex:\n", + " print(ex)\n", + " subscription_id = \"\"\n", + " resource_group = \"\"\n", + " workspace = \"\"\n", + "\n", + " ml_client = MLClient(credential, subscription_id, resource_group, workspace)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "workspace = ml_client.workspaces.get(name=ml_client.workspace_name)\n", + "\n", + "output = {}\n", + "output[\"Workspace\"] = ml_client.workspace_name\n", + "output[\"Subscription ID\"] = ml_client.subscription_id\n", + "output[\"Resource Group\"] = workspace.resource_group\n", + "output[\"Location\"] = workspace.location\n", + "pd.set_option(\"display.max_colwidth\", None)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data\n", + "For the demonstration purposes we will generate the data artificially and use them for the forecasting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "TIME_COLUMN_NAME = \"date\"\n", + "TIME_SERIES_ID_COLUMN_NAME = \"time_series_id\"\n", + "TARGET_COLUMN_NAME = \"y\"\n", + "lags = [1, 2, 3]\n", + "forecast_horizon = 6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Synthetically generate the data to train the model\n", + "n_train_periods = 30\n", + "n_test_periods = forecast_horizon\n", + "\n", + "from helper import get_timeseries\n", + "X_train, y_train, X_test, y_test = get_timeseries(\n", + " train_len=n_train_periods,\n", + " test_len=n_test_periods,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=2,\n", + ")\n", + "print(X_train.shape, \" \", X_test.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see what the training data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Plot the example time series\n", + "import matplotlib.pyplot as plt\n", + "whole_data = X_train.copy()\n", + "target_label = \"y\"\n", + "whole_data[target_label] = y_train\n", + "plt.figure(figsize=(10, 6))\n", + "for g in whole_data.groupby(\"time_series_id\"):\n", + " plt.plot(g[1][\"date\"].values, g[1][\"y\"].values, label=g[0])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us look at the train and test data of the synthetic data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a copy of the X_train and X_test DataFrames and add the corresponding target values\n", + "df_train = X_train.copy()\n", + "df_train[TARGET_COLUMN_NAME] = y_train\n", + "df_test = X_test.copy()\n", + "df_test[TARGET_COLUMN_NAME] = y_test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# For vizualisation of the time series\n", + "df_train['data_type'] = 'Training' # Add a column to label training data\n", + "df_test['data_type'] = 'Testing' # Add a column to label testing data\n", + "\n", + "# Concatenate the training and testing DataFrames\n", + "df_plot = pd.concat([df_train, df_test])\n", + "\n", + "# Create a figure and axis\n", + "plt.figure(figsize=(10, 6))\n", + "ax = plt.gca() # Get current axis\n", + "\n", + "# Group by both 'data_type' and 'time_series_id'\n", + "for (data_type, time_series_id), df in df_plot.groupby(['data_type', 'time_series_id']):\n", + " df.plot(x='date', y=TARGET_COLUMN_NAME, label=f\"{data_type} - {time_series_id}\", ax=ax, legend=False)\n", + "\n", + "# Customize the plot\n", + "plt.xlabel('Date')\n", + "plt.ylabel('Value')\n", + "plt.title('Train and Test Data')\n", + "\n", + "# Manually create the legend after plotting\n", + "plt.legend(title=\"Data Type and Time Series ID\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import mltable\n", + "import os\n", + "\n", + "def create_ml_table(data_frame, file_name, output_folder):\n", + " os.makedirs(output_folder, exist_ok=True)\n", + " data_path = os.path.join(output_folder, file_name)\n", + " data_frame.to_parquet(data_path, index=False)\n", + " paths = [{\"file\": data_path}]\n", + " ml_table = mltable.from_parquet_files(paths)\n", + " ml_table.save(output_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "os.makedirs(\"data\", exist_ok=True)\n", + "create_ml_table(\n", + " df_train,\n", + " \"df_train.parquet\",\n", + " \"./data/training-mltable-folder\",\n", + ")\n", + "\n", + "# Training MLTable defined locally, with local data to be uploaded\n", + "my_training_data_input = Input(\n", + " type=AssetTypes.MLTABLE, path=\"./data/training-mltable-folder\"\n", + ")\n", + "\n", + "my_training_data_input.__dict__\n", + "\n", + "#Test data\n", + "os.makedirs(\"data\", exist_ok=True)\n", + "create_ml_table(\n", + " X_test, #df_test,\n", + " \"X_test.parquet\",\n", + " \"./data/testing-mltable-folder\",\n", + ")\n", + "\n", + "create_ml_table(\n", + " df_test,\n", + " \"df_test.parquet\",\n", + " \"./data/testing-mltable-folder\",\n", + ")\n", + "\n", + "my_test_data_input = Input(\n", + " type=AssetTypes.URI_FOLDER,\n", + " path=\"./data/testing-mltable-folder\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compute" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from azure.core.exceptions import ResourceNotFoundError\n", + "from azure.ai.ml.entities import AmlCompute\n", + "\n", + "cluster_name = \"forecast-function\"\n", + "\n", + "try:\n", + " # Retrieve an already attached Azure Machine Learning Compute.\n", + " compute = ml_client.compute.get(cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ResourceNotFoundError as e:\n", + " compute = AmlCompute(\n", + " name=cluster_name,\n", + " size=\"STANDARD_DS12_V2\",\n", + " type=\"amlcompute\",\n", + " min_instances=0,\n", + " max_instances=4,\n", + " idle_time_before_scale_down=120,\n", + " )\n", + " poller = ml_client.begin_create_or_update(compute)\n", + " poller.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "TARGET_COLUMN_NAME, TIME_COLUMN_NAME, TIME_SERIES_ID_COLUMN_NAME" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# target_column_name = \"demand\"\n", + "# time_column_name = \"timeStamp\"\n", + "# general job parameters\n", + "timeout_minutes = 15\n", + "trial_timeout_minutes = 5\n", + "exp_name = \"forecast-function-exp-no-target-rolling\"\n", + "# Create the AutoML forecasting job with the related factory-function.\n", + "\n", + "forecasting_job = automl.forecasting(\n", + " compute=cluster_name,\n", + " experiment_name=exp_name,\n", + " training_data=my_training_data_input,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " primary_metric=\"NormalizedRootMeanSquaredError\",\n", + " n_cross_validations=3,\n", + ")\n", + "\n", + "# Limits are all optional\n", + "forecasting_job.set_limits(\n", + " timeout_minutes=timeout_minutes,\n", + " trial_timeout_minutes=trial_timeout_minutes,\n", + " enable_early_termination=True,\n", + ")\n", + "\n", + "# Specialized properties for Time Series Forecasting training\n", + "forecasting_job.set_forecast_settings(\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " forecast_horizon=forecast_horizon,\n", + " # target_rolling_window_size=forecast_horizon,\n", + " time_series_id_column_names=[TIME_SERIES_ID_COLUMN_NAME],\n", + " target_lags=lags,\n", + " frequency=\"H\",\n", + " cv_step_size=3,\n", + ")\n", + "\n", + "# Training properties are optional\n", + "forecasting_job.set_training(blocked_training_algorithms=[\"ExtremeRandomTrees\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Submit training job\n", + "returned_job = ml_client.jobs.create_or_update(\n", + " forecasting_job\n", + ") # submit the job to the backend\n", + "\n", + "print(f\"Created job: {returned_job}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Wait until AutoML training runs are finished\n", + "ml_client.jobs.stream(returned_job.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieve the Best Trial" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import mlflow\n", + "MLFLOW_TRACKING_URI = ml_client.workspaces.get(\n", + " name=ml_client.workspace_name\n", + ").mlflow_tracking_uri\n", + "print(MLFLOW_TRACKING_URI)\n", + "\n", + "# Set the MLFLOW TRACKING URI\n", + "mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)\n", + "print(\"\\nCurrent tracking uri: {}\".format(mlflow.get_tracking_uri()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from mlflow.tracking.client import MlflowClient\n", + "\n", + "# Initialize MLFlow client\n", + "mlflow_client = MlflowClient()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# job_name = returned_job.name\n", + "\n", + "# Example if providing an specific Job name/ID\n", + "job_name = \"yellow_camera_1n84g0vcwp\"\n", + "\n", + "# Get the parent run\n", + "mlflow_parent_run = mlflow_client.get_run(job_name)\n", + "\n", + "print(\"Parent Run: \")\n", + "print(mlflow_parent_run)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Get the best model's child run\n", + "best_child_run_id = mlflow_parent_run.data.tags[\"automl_best_child_run_id\"]\n", + "print(\"Found best child run id: \", best_child_run_id)\n", + "\n", + "best_run = mlflow_client.get_run(best_child_run_id)\n", + "\n", + "print(\"Best child run: \")\n", + "print(best_run)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Print parent run tags. 'automl_best_child_run_id' tag should be there.\n", + "print(mlflow_parent_run.data.tags)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pd.DataFrame(best_run.data.metrics, index=[0]).T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the model selection and training process. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Artifact Download" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Create local folder\n", + "import os\n", + "local_dir = \"./artifact_downloads\"\n", + "if not os.path.exists(local_dir):\n", + " os.mkdir(local_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Download run's artifacts/outputs\n", + "local_path = mlflow_client.download_artifacts(\n", + " best_run.info.run_id, \"outputs\", local_dir\n", + ")\n", + "print(\"Artifacts downloaded in: {}\".format(local_path))\n", + "print(\"Artifacts: {}\".format(os.listdir(local_path)))" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "category": "tutorial", + "compute": [ + "Remote" + ], + "datasets": [ + "None" + ], + "deployment": [ + "None" + ], + "exclude_from_index": false, + "framework": [ + "Azure ML AutoML" + ], + "friendly_name": "Forecasting away from training data", + "index_order": 3, + "kernelspec": { + "display_name": "sdkv2-base", + "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.9.20" + }, + "microsoft": { + "ms_spell_check": { + "ms_spell_check_language": "en" + } + }, + "nteract": { + "version": "nteract-front-end@1.0.0" + }, + "tags": [ + "Forecasting", + "Confidence Intervals" + ], + "task": "Forecasting" + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py new file mode 100644 index 00000000000..56135869a89 --- /dev/null +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py @@ -0,0 +1,64 @@ +""" +This is the script that is executed on the compute instance. It relies +on the model.pkl file which is uploaded along with this script to the +compute instance. +""" + +import os + +import pandas as pd + +from azureml.core import Dataset, Run +import joblib +from pandas.tseries.frequencies import to_offset + + +def init(): + global target_column_name + global fitted_model + + target_column_name = os.environ["TARGET_COLUMN_NAME"] + # AZUREML_MODEL_DIR is an environment variable created during deployment + # It is the path to the model folder (./azureml-models) + # Please provide your model's folder name if there's one + model_path = os.path.join(os.environ["AZUREML_MODEL_DIR"], "model.pkl") + fitted_model = joblib.load(model_path) + + +def run(mini_batch): + print(f"run method start: {__file__}, run({mini_batch})") + resultList = [] + for test in mini_batch: + if os.path.splitext(test)[-1] != ".csv": + continue + + X_test = pd.read_csv(test, parse_dates=[fitted_model.time_column_name]) + y_test = X_test.pop(target_column_name).values + + # We have default quantiles values set as below(95th percentile) + quantiles = [0.025, 0.5, 0.975] + predicted_column_name = "predicted" + PI = "prediction_interval" + fitted_model.quantiles = quantiles + pred_quantiles = fitted_model.forecast_quantiles( + X_test, ignore_data_errors=True + ) + pred_quantiles[PI] = pred_quantiles[[min(quantiles), max(quantiles)]].apply( + lambda x: "[{}, {}]".format(x[0], x[1]), axis=1 + ) + X_test[target_column_name] = y_test + X_test[PI] = pred_quantiles[PI].values + X_test[predicted_column_name] = pred_quantiles[0.5].values + # drop rows where prediction or actuals are nan + # happens because of missing actuals + # or at edges of time due to lags/rolling windows + clean = X_test[ + X_test[[target_column_name, predicted_column_name]].notnull().all(axis=1) + ] + print( + f"The predictions have {clean.shape[0]} rows and {clean.shape[1]} columns." + ) + + resultList.append(clean) + + return pd.concat(resultList, sort=False, ignore_index=True) diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/parallel_run_step.settings.json b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/parallel_run_step.settings.json new file mode 100644 index 00000000000..8be91e5cb2b --- /dev/null +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/parallel_run_step.settings.json @@ -0,0 +1 @@ +{"append_row": {"pandas.DataFrame.to_csv": {"sep": ","}}} \ No newline at end of file diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py new file mode 100644 index 00000000000..2c6cbb98161 --- /dev/null +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py @@ -0,0 +1,118 @@ +# Generate synthetic data + +import pandas as pd +import numpy as np + +def get_timeseries( + train_len: int, + test_len: int, + time_column_name: str, + target_column_name: str, + time_series_id_column_name: str, + time_series_number: int = 1, + freq: str = "H", +): + """ + Return the time series of designed length. + + :param train_len: The length of training data (one series). + :type train_len: int + :param test_len: The length of testing data (one series). + :type test_len: int + :param time_column_name: The desired name of a time column. + :type time_column_name: str + :param time_series_number: The number of time series in the data set. + :type time_series_number: int + :param freq: The frequency string representing pandas offset. + see https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html + :type freq: str + :returns: the tuple of train and test data sets. + :rtype: tuple + + """ + data_train = [] # type: List[pd.DataFrame] + data_test = [] # type: List[pd.DataFrame] + data_length = train_len + test_len + for i in range(time_series_number): + X = pd.DataFrame( + { + time_column_name: pd.date_range( + start="2000-01-01", periods=data_length, freq=freq + ), + target_column_name: np.arange(data_length).astype(float) + + np.random.rand(data_length) + + i * 5, + "ext_predictor": np.asarray(range(42, 42 + data_length)), + time_series_id_column_name: np.repeat("ts{}".format(i), data_length), + } + ) + data_train.append(X[:train_len]) + data_test.append(X[train_len:]) + X_train = pd.concat(data_train) + y_train = X_train.pop(target_column_name).values + X_test = pd.concat(data_test) + y_test = X_test.pop(target_column_name).values + return X_train, y_train, X_test, y_test + + +def make_forecasting_query( + fulldata, time_column_name, target_column_name, forecast_origin, horizon, lookback +): + + """ + This function will take the full dataset, and create the query + to predict all values of the time series from the `forecast_origin` + forward for the next `horizon` horizons. Context from previous + `lookback` periods will be included. + + + + fulldata: pandas.DataFrame a time series dataset. Needs to contain X and y. + time_column_name: string which column (must be in fulldata) is the time axis + target_column_name: string which column (must be in fulldata) is to be forecast + forecast_origin: datetime type the last time we (pretend to) have target values + horizon: timedelta how far forward, in time units (not periods) + lookback: timedelta how far back does the model look + + Example: + + + ``` + + forecast_origin = pd.to_datetime("2012-09-01") + pd.DateOffset(days=5) # forecast 5 days after end of training + print(forecast_origin) + + X_query, y_query = make_forecasting_query(data, + forecast_origin = forecast_origin, + horizon = pd.DateOffset(days=7), # 7 days into the future + lookback = pd.DateOffset(days=1), # model has lag 1 period (day) + ) + + ``` + """ + + X_past = fulldata[ + (fulldata[time_column_name] > forecast_origin - lookback) + & (fulldata[time_column_name] <= forecast_origin) + ] + + X_future = fulldata[ + (fulldata[time_column_name] > forecast_origin) + & (fulldata[time_column_name] <= forecast_origin + horizon) + ] + + y_past = X_past.pop(target_column_name).values.astype(float) + y_future = X_future.pop(target_column_name).values.astype(float) + + # Now take y_future and turn it into question marks + y_query = y_future.copy().astype(float) # because sometimes life hands you an int + y_query.fill(np.NaN) + + print("X_past is " + str(X_past.shape) + " - shaped") + print("X_future is " + str(X_future.shape) + " - shaped") + print("y_past is " + str(y_past.shape) + " - shaped") + print("y_query is " + str(y_query.shape) + " - shaped") + + X_pred = pd.concat([X_past, X_future]) + y_pred = np.concatenate([y_past, y_query]) + return X_pred, y_pred \ No newline at end of file diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/images/forecast_function_at_train.png b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/images/forecast_function_at_train.png new file mode 100644 index 0000000000000000000000000000000000000000..37d8ffddcae6028284e6f25b6bf146542509e57e GIT binary patch literal 70504 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

P{La7N&T z{-D3LNh7{o`)ZKnBUfWyToz{6t%;gk3{JV;L!FT$jxasjFI-pcrE|wH%38 zZ*~%OD8POTk~N63xX|n_J2%h5eNiH{S9~Dlm#b2mr5{|-Wy?q&yjCbPOOyuB`F5*lr8!3kNpnsH+R=F-^D0iy?>pG{&;Z zizVqTjo*6YuMk(3$t9m*802=vrhE6?)5*iNhPDe$*LnlE*T=~!ar_xlKhfGRSB_qZ zwO;%F>y{G*YU3KCEv(xM&4O~t;pWzY${7};yVoU%KX8djDt zi9QJ|n*L9u18M`)-w6=x!rR1`!NcxPnq9};{8;VPp*ooYyf3!(FTKw88xLykzG-JV zn;ngI>fF%N4v>qaSlSAmsEfjXwz>(qy|e(L0Ca*XsRX*?3x?NKtpS@c7#}=PSTUd) zwJx(?0_IOKxYO%1q5AQ2VH)vhljklP0yFiHf#VPFTHi@mRxNqBE0=oC)x=CK0WO4d zT{6-Gr&?Us3!4+BXpLocX3!p)&==R<;vyga607}0mE5vT#ANj(Or2{&iA{*wusOZJ95h`RKKN&iYwXkn*Pc+%{H#xkI0&uGV5L6pN?-ro-sNMR z8f!-EiigdvhhzCLZ+#??_3&@uRL4znq{M3T@mZgzz%Ki)YF$@1RSv@aTW!b62i*{z ziyl(kV^F&Nlz0UFvnxrT^+MNE9x8NTj7o5AeVxv+oK^c>y;uJzP{#r*R9n&lEBtRy z*xw&?uJH3B0s2@Ac^Qkvp35)(ci+vKuOd=-80OR8NN-ll z0x1*(PQpL~8t`rC8(d(J>#VT2>)h^a2Ua?W_3`^B7fng+S{uC$hRJ0rJP?^&)Va%O zhV28$WTKKSiUTk!R%UQ_cPD0GHCbUMyPb>oly7N&-{x}fIl$Q)Lur)2sxBJhVu5D0 zcf~w>Jt#hsWB*r8k)3vpvty-{m;%K$^y>mCx+`cn9_WHa;d(xQ^8u#oM*bBAnZu`9 zg@JIjJ0fmMP6gIv(v54>uM_ZD2m~>en_86)Y^2DZB=&v$mW-wrXz8$5Xc8fGAC{d0Mr=j9uJ&-pvO^gp9+fAsMG+;m^rZb3o8 zdpd$!!n?n7g+94HAQr)DZ9Qe>OyzuRw9gzEe^pQR47qIg`1n{%OpJzxMn+Z^7=!xr z8Qy&4vu4JbU~FO%K_R@oz8)JJONV_wM+pM?NCtvfvI+{A{Kb}cb{c_cEMU+=ztu}G zZ|q^j-{HnLt74$oNd3}?hzOC7+k~h~^Nr31b#~OOtT@gc_Y<2S5Ck8ijT4yGqM)F- zKHIzB|4tQl-^^=m^)@rhIs9khk&EVmVg06KgP)%tfI{2FmXx8klfFy%#)l6d21C~Kgh%Gr#G^-we=UH2Bx|0KR!QwK`e1AD=R%cJ#B4m zmEfWMnd;EYgSEam6l7#bx=ao}**~{$A_|I?oxlNR%)D=ox`8MX_3hhxb1p^5gFjaS zg`py8Iz>iC>eSlgfW90c{Z%tW45O_gG$?3k2t-d$kM;TQchg6xL4COYOmntTQ2oyz z|8MJ{?S~q%T}Yws4=?$xE>ao)Pk>5 z00#sjSKCnxs)J~$U7WTZiH;qKw%vxd9e6;b`Cz5On`DMd3XL}JWZ8lDgR8flnP{>S zD|Tt_2@S~=?#6d&_KG;J3pIIZ(pf5ObO7;JZ$Ponfbn6z*D-h- z2uflf;e(|ciZ$f4Rb+j`Ppk##8o%kPEW)cucfS-KAnflD>zv-J*lX>FmJBHYEmpW~ zX=QCJfyZ`i_IxCxiLzt^YHydZ0pIk2{;RzR)FtoA53Qo?_N20!7};%-IpHR0y3m&K zhq~<;gK+u5jWpe>(&Rh3zT-I1?tr~o`yrZX(NdKK%a!HW0A*q9G9zG(|58J~2R|aO z4$fLvR0`!k?1eB7lWFeYc|U=gCAUHwJPUC z>UxvOBlK4)VCA3kUujj|%qI&36mnKbxpL*-r0i0+h#oPPaHipu_OFI0eBigLKe^nc zULGOPH9Je&P-4!Uel;pCO;8_gv3p+eMxLn0tk@97B9QazV_CK?*-(E+ETeb zkZL$YG7Ajm44HUaew*Iw#Kf!AC_Th*r)30tZJ~pxx+K=&-*ME4jdKyc2?! z4AX(1X%M$1*ywi2v9rj9=@YmScM%yKX;tFvijeSi0CVEFm;eg_`NPtMCg_g+8f3SG zrF(@OyBY1p(r1CMd6UH@p%qO7nF>sXHSd}$0jSc@w)0YOc-U$eVzo<99OJ)Br?7Bt ze>YU7BYcTS;>NP*s37(4$RyDH4c_Hq!N~^KODddj#tD38ZXGTw5vGnby9TIy5 zq1&%Hs-|aHg>*RAzBE_!J4pKluDWjHtG2pt(a*~DWrCw$R9jdpe=6oXK-YYg7+7|$ z>P@JEN>&Im+b zR%?tlWV{WiS{K?k9Elbqx&7^((MBrxbu72cRw;8%!YHER>1{TLbn9D3qYZjSE=lhJ z*M*My#h6MbQxvq4PZ9eXkhT|pwJ>k50cVCN^L=$arzFnV}C(!wW zzH@n8w3ziTeqx`F1(PtwQYO!7>oFP?{WX$=8F*{nplPY;9fgx*qIp07JmR?3Wxo3^ zLXqN^d9IMJr)m;nie~`-Hk*eF^@{WgSMWTXoeKz!Qhq@80p#3v9>$>Di?<2e9$5Qd zE;~)80$JG9-&XiYMJUgOZ4H(5<;2GcMW$>wP5FksBUOv)Ve3A*b<@>#XJr`J|3ZkvQ6x zuA^-%xol}X%ATr|SluQ=+;T5!t@*31Yj&R70wy*qqNLXYuD|K6b#q8Gh}$b(sx8P9 zw>R&by&^s(imUfcnKRc=ht)k0xDhALV5|s^b32Q8 z=tkpqD}3-Dw>04*Su^KNe06{x zzOF-Jb-!-Ae^ZzF#I?SY88_qWAzy(;pG#66*%Lydm&qd@9bi2)tnFwol)FAU zjuwi&@}!RA_|oV^ud_`^a^@+|0&4wX&VGo>W=rB;ZDGz(JGPyU8obwS@y*X32+vP3 z{teINLm)(84xdxkfjY4b<1wj>)|YX!$|t*Ujz$_@SE0OCd!u5dE?NZ+1I}9pLFU3^ zaNN`D*PUX|X^oEb*GlG?W!`=LHL^1C%EY>pW8`iU^9QuTL zDl7|v7QL|hl+oYyuWkT<<*LI_73HdA@d)#%sen1Y4?ML%iXo2L( zx+{1Q-0ygADVE5cKZRe!)2iwJLapJ8<{ zL+o=Nzj63F_N=enpT`df&aWta*s&1LhF>HpWFz7v+_WEfzQp>;pEO0I*mrR-9Xga1 z=dZ6{dObBgAH9{VWP;mPTJBBbR|^i6-XzM>_;$>Jx>Sc`y#fStT=aj#QYYx?6;Go{ z6m3Wa|IYO3&p2BXuuq?_-!c(w`r&M$_o@e_iL)3Rp4r2JK;%Od-JhkpYB|GSD!3$6 zoNO-Bbaz2#uG%uiU3r=qEX}>fmE~Xh<~v<)Qp}d(U$;NYV?bAC7?|!SqA=7@RQZnO z9ygy7nu4V7w2FP`xf=pW75yduY{O>N7`>c6^DWtIglAwBThzRI2r|cj_l(eT6-U>S zC2tTg=HF$VQ_7*ZJlOIkJ_RWkq}v~pY?fAhT9?u_c%vtl)Qem*o?2UZDV<>X*oSgy zC9oo5D})85Y%~gov2~C(eCa05F#k0e1>9g?y5%cut*6+IXL}ANgXNY9dFJg=p>*VzPW##%kJI3sbn|up$ZE~<%tO|m-@qw1SThXaIZEA z$D~C=R(A zKSs6qsB~BQY+N&t_t06fVWzz_sQA!#v{(r*i59Y;l1pw~ecAZnO5cf2%b-(!FTk_Y z!Qn<>mqPmvVPuq)X(I7J_Lz_gB@GPIP#NjWYUNqiwB-cGQL~+fra&xQlM!1)m7H%Rv6)#aW+!~VQif{(NyS2MK8kwWayB!m| zi~co!dU+dOQetINw?4=i$hilguafbwri%&9guv`KJ=Y91^ z^(|VI6IYz~iO+Vqh=m1TqX4O6IL5y|%Fv{gm_u)@{N{yS#)tsg|{DLa*!mul*crQlBkqPinzezgi~FC|OqZxL~NgVdr+gqNe6! z81-WV?T$n$u={z_g9B^98e}`1_3a(ICoDJ_N({7DA_J(+#x1ZT&s{%`0n0NoCwoqR zfL5kG3vMYkGTj_pb+oE-!5XL)jxt9{>j|UgCmQRzS1{RhAn*Pxd}@Gv$bUw-i+yP5 zG-jR|zRF2l$=tyB2`(!NU%h_O2v^@Sb7Gs({CQl1P}!A8pDL=rZi}0SAy?+5gFIqY z+{zMuqj2D4r&T$gT&c|D*_HH}Z}@%gca9=9fFi(C-Cxv@+P8K>?9M135c|yDaf3v< z$i{@Ml&9VR#MTY;5B}yc(PE5}bn;-z^NAO#h#;1T6@vwNr$L^N0);>%HSgj2Dz#O3 zLem&ZM9bSXu?>5og2wiGV80`oCuzPTxU@fqg_tZ_j2v^Be`iYL(TF%{YlT(z>7_Ma zn2ueLouTrKatPeCAh5Kh`^mI$K@_LESg*^NVFW*72wKjtj(QfZ01op z!6YeM3N<0ZFGgRKqvoNx&hkN-t}SVKI3k55Mjg*6k-U@%zgSez=1r>{hJJ1FNjX+? zd+7WzH@WE8#GvLaJMx-S%4M%RqC=FDG{tv7n zolNJ?T;G;6_}h1JT{#b{;|H$N6^>P37ovyg=@L^W|7YbVxWHwB?wT3c1XNP6@&#?s z^wAyRh+R~qu&Nn;00_jD!SozFYJY_;6&*ei=VCgH({C?FpbaZqdu6sb-|Ly7btLjjBEt|ddJ^p$zH z>d*Ql!g}DLNuOM?tvFwmWl=T@7LtW1}K(_eSl zZ*V}vs_$cxM?-CN z{|PE6JwN_LSBna$8?e0m;TFGK=Y9?{C@U2mH^jl+pPJ>Z-BRgD-6S5@rQDKU@x*Zk zGdFjVsW?=`r$ve3(t6Ig_do}k*# zdaPBzFfmL7U`4xazLFD6pp5Wu9+GF$=T5r|yZWM{yBeJes4e?tdm^y1USq02D06e0 z1^!fbO`+wSJD{<)PeAAIUaE@Bo2B2GM+!s=UL~eK3%kY)7*nr|kE-IPhVt3V5|71x zwd3_g3~~{_cnB;y^|{xhzq`JcD3vs&AX23iMlsbpC)N}LC9jRwk5?SU1Ic^ zzo38&Mc$L0;k4O{*Y)Q-4Lm@q(Eu7(RiwJhN>07^)=0R&?}ap^5{8#C=A@j@d-2|@ zrsQ1ME`C57OAGz8X&t zsgzZl^-{Ij%&TuIk(xtnw)uZ zN8==Ff`LiB(}C5FMtRXY*XojbzjgjD;X`=81S=?siEw*NRwmA?NcUQ9I&~x`be5SH zhch2@lUi>na@0(ZxkKTY70@m4`FEYLkhuz|NInlv$(LsHo^ZBBUis=ZK@}}(;cP5{ z0?i{Wvrf_$+Q0Yq?h_C($WiR1b1lcxk5QruxO$nIOID_=C)R)DuADG^miKgJ#cGQr z@yq>t0u>TqL+hpo+6e|N?o|$Yl*Nm$J)6J@m(KI2l?`Q41;vZZFt03y!^esLNb=xb zk|R3J8HeI?`L*qZ99e-v9_;iqKnrh2G|IQ(qyCgJl*z{Zp(Ow96jsm&H98ZehV@>z zHA|T$%Gg0$9RY-}I+h5-?49h|=?AqK%Zz`;xZge?M=48?j?^HvuUQ$-tPo!9bqZry z6jc9UUrqh1RYr%c>aPSKi+gTcr5%p3^vo=!e%@5j)cKVtN8ur7HX8Y8$9oBjpG}>; zQ&tUMR~rt;0{VA!)nYgRPQ3rjJ^ds7!`OoKo8hX8l#XhzYsVkmIHZr&B!zK!iE|3s zi`9zu^%@SvyDDtf0`*QTsMQ)Uy;%O9UPpS51Tr6ZU(Ky9XR+GW3U0_+S~O*HfdW=B zr}{R{AOp-xg|kuaS3gYFyh*>|yXR#_e!w`D5!XK#0G_(NXSEpD0ue6;dsnR^9p|pz zA(=QQH3Lnk^6!G7QU<-z`kS)QN!!w^70b1m*CA%}RbJPABY!WjME952*#^|16qz%8 z>Bm>DdcRQtYp=O?CxpCWmS<5cK}^QSy@3|IM}xMBcG(J#lc*n=Ac44inZ$FbnF~12 z5?}w2OUaS8F9bqR#gxpt|DvRs@6~^o^M4T{aNkTE>8igIQB)2 z>pvV=`4Yqo?{$t)hi>K_O=TWU70gT-TwcSrWm^k*@YIWT^N(iqNe|RW-PJsuEVjO} zZOdBSOHB4jN$0E^&%fhNNocvXD=JNbUtL&*t0~8Lt2?0RozX`WAW%H|$2%Q~v|F1C z!cb&Ls+m(3)t6_29ZHQa>APO(GbfzpuAG;u2?j92PFWKW`heZE5x4CSr#E>_gSbL* z9rHc+BV~9p*D$vhjx8#Ej7L5=H$OsyERr@ zvQnu6o=Tct?i}Bn>Jr<_AHUWBMe%qPYF?FFihED&zYM8fjP7@@9j`W-z6-vJ1{tvk zDkeX2urLPKzK+nlI5I1MGZ6NcZ=VdSYj}ClLbsC&>TP2%BlB@+mshq*i5r`IEn~TF zW4a~V4745Y7n4{EyFLowg-^!`FzEb1@PGBlYR+0HJBaeZ? zs-$QvFHqg0&u?V+jaL;S!C=7wag~}qqCg++CgAk~)8FBH2>H=s=g81{eN%4y?7E~W zC%R!=Txedk821=(=;cpyzv%P70e>pHpIwAg<%4VSp);^Imd`bt-9vm=MSfW>O4423 zcn6P4M77oEw^iZ6^B3b+jY;y39bC?0?~~odnvHcXXrrkjcycyPuF5+(S`9(e1>WNG;mx5v%BkVJrgQkRuGo-IUfwvcLr{{Qj z`FAm;?~V2G`E5(r;H|uecJ--y6ph9vYI=P>=N5DL09)#f?yoPf`&Xf~mER-#r)>I! z%xM>Q2msu>3sKK~-mndxfoi$+neX)#@+5X`**%YeHC}=W*K3q+T*EN{2_Qq z{DR(4e|=D5M#w5WgkB`{{sk(yctA$RlL~~aVm_Wg&s>{rten;wm77Vu@*-w~9IQo+ zl7l=29DLxC4-5%=>t*;*gvM1Zozr?i!3Ee2Az77n_mySxA2P;nGxG<$H>TgE*>M@Y zh`5QJ_*puG`q?=BypLO;wlEFv>tC2pFZ{% z133XC4JcCRA7)JCEIvp*79V2@AIRt9nawmGd2{L?^V8Sueep|b5HBq8T7Jv7CI`M~ z(yAd_|6##=v0egiz#T?Io~5{MSqIWpQ_F$vRBIP&7qcOl$sn8weonlq>gt$yi*Nos zCK|-mG-Jxl#AL~nOS+^pnkCjqZkd9VHKJLZ^TY5F#Wizcg?wIC@VG!AjoY|`@5=b5 zS@~0WEU~Cj2v;nMRf>4I6Pn*FSbkM}f3G4PE3NjXn^{a& z@H1#`x4`9RA#J60++LC*kdu*Y=gthL;d;xOx9D>=;s~xaF5TWUd5gQFs8nx6KZD+t z{m~cLw8b*nm?*ymw=M`6y_>JH7#k8n%;hPh={LKbR+Sldg?#yf9TF*~65a?r7d==1 zfi=nV?s6lPggrbWf<$}n5fPC@M<6!o@hQvzOV0i5X1lO-y+0u;A%T&LGfM>AAO8|E zOy#BuaqejM#{#x*S-?KW8$Ujy1DnmvRh!dM=H?rNDeWD^kfO~^NaFF*kI%s3slZdm zLWde#TU)?OL>4{ZVI1|T!solw;vJ0UX?*r8fs|fL=%ba21%LsIE@WEq-awB#)qGXpeGlY(c1wk&Ei>gNQW+9)Y`|ED=k;DKqliJhLr&4| z5XJHDUs2GRs(SdKpn=OWYKBD|+sXB3yw{s6EBGWN^i`{MOG``Mi0egG?JCGbLCjk8 zuOh8Ve0=;1(&3#`BO@a&Sz)&m7vRMt{=|9>9v91jgzdaGbMdjUKNY@eC@WV$XL;Nt z6OTm@m%yy=&={d#cRw~7ik!#Y&1Grp-4){QT*S*VMc`wmhRONUfN)aU>r>YN0-}wX;fzjM2?*hxgFYCSaX{_!=-rIJq)B_ z-)lVbW;owgFV7y|65wbl{e|RB2QyF?Q;AFR z6;JoZ7xt<(L*AW^D9P*NkoW)(5-k7-*husC>WyY3bPi=vOy?$>c5YQt7y7t89;NX5 za#$Si_F^?+dU{$cG&6H^bMv4Tk%~`;D%RV!a@t1d!+i>>+8!x zy}8;c{ovrhMTV0D@#1Udl#OG%g|*&EWbjf^`A}-=>RR&$o&xU%0m@aBdvf$b)6~hW zZ^&@esx^+&2+0!~Dv?T=Xqd`TI~tGM%yaNG5Z4YG9v`Fv@wO&TB&HmsTuNSklK!cD zcBhQ-yU`}j>({UEU(oTD-+rYN@O3XLRCCOn=uh`=DWUeGVaQpV zwP`E{52YGE#lggMyIhL{hAlj|Mrpm?$q~MVmFUSnyt#+#NR|{Udk7scs&V3|rKQ!r zXpWBi)Mcl*77|PMKK>2=4X|Em>{l!-EDIj(8f$*=;M3mncExZk9Go0}rNW8!Mc&-eeKLyGMyDn^U7|#LpBD=ktX$~+$*~)vDHHU)3`2~& zz>Y=1pfCRiU2h#$)wcZ)Z$#ushzf{+z)@7XOQaM81nF*2x&`UlpaM!7bZzMdr5gkR zrMpXz?uJdhV|(s7_xHW`zUM!m=R7X=Tyu=^sX3R$_ap+YL(Rr5`Jz+YtRhCkhxWsJ zPaBo>nMY3pIoN!-DCn;;sUsh<1P+_NvJ48bpR-s%V0sp-#^+B-Nut-$>S4hQlQo-7 zlnDt5^`C0*dmL=^%8SS@hE21g_ysbFtfq?Bq)qH}x4gBw66AZ;QJ3UWoPxfMG z39}*iW4587fuzWBY2|fB%iZHn-o_=aUW5;H)09>w!b51i#`l<^$|qt_dh>vu!%T1 zhm9?AywT81?QR8q0%lz5U=NBkJ(6A>sVJ2=Xw>Cq zqn@*?o(BpkPJDwMj47u?GB|D3~ zakO5G+?aY;L&y)^AD^viBNTaT*b&D+J!~S3sD-V=EuDKYP9}zXu|r^e1tM3EACE`= z>{80nudPu|mkS#rzIxT{I}{A;b!06J0uqPDkJS{xCan=Hn9%^OO5bz0ANq>g(Yw!g zFfZR*`(F%JQ?apo*jPYvf!H(Kfs|sOB{eTzbrh+F($*_+q)&a{5>+8D47V0d&1oO6 zegf%XfbQsppY+FDgDEUrbB(=z>Vok?-$bo~zuZ=#+@>4S2Mowu>3>ZYSO4zH`sq@B?7u@{jzFsZ))-KE!pNv)E)xp6*sL1I7OocQ#)l8KwLshZpcS&rrztT}& z(`<&q?t51Lm#EAh!}dPRl#RP*H&y3DQlhJ?+fZMBa7D_@Y=J4iYHOo`Xn&#G5Y7O4 z%zQCy&eN@osFjJidjQR0@ z*iwNs`>CI0-$rusi|=1!(NZ8-8!ctyDRWxYYR}&j$4ve*QYe&ceMH(TD&rcdaQVd2 zE%f0QZ7)xx7azm+4#UH&)!MK&*VyrwW^%CwR1MF`%v2f%+xRl2ktYhCUdG{haRX^m z$vtQq1?Hm^mw@N%+0*3tr1F-HdA=sD2l>g9IVLrVoaQofVuPo3m(Fk+cs{q!lk*08G#pGqtTL(Xl?wa1d;`Ld7br>3L?(TJQ* z!a#_mNOaA_r60THIcrB$73&TsC2T z4#Su`jr*Jm@jEQ+3st)V+Q(#6o_oWl>iK%4@(SA+%!#nU!H8I--q{Wo!AEz4=V{`8 zD5z#;2G!(jk6SV_<#4K~NyMpIA7$>8t{J;GFV@S&Fb@4VFYp>MR%JOy+1RVcJj(xa z{3u`scyJa~gY;aDa{bl(LzC`$p0?xlIRY6GhUtQhV?XwHC$7o#RI-*BV%+!^2kl~; zJ4xNxlef||*RNF|V&~|vodbhRQBjeE4G@#>ds<2K;i2LU(%Jd>SedlCx(5LTVR8&u z6X4nP-4JxyI6IjbOrwN9S}mA6dQC!qhL^OnKwR@m4Ea zFF}sRmzN2Fwlbdgb|;R9M_#@>7qCF$O(z?pVj3y8h~A$>B7eP?tZ-bG*tTWAzT@Vp zsW}X#Yi^?Fu_?~SS87pOmGll39xfjE*>+MmrU8m(9|ne_vvaAHA7;)!X9BvRUW>?4 zi&&*r^)e{fNPlRn^lP&g%2Le4$T%tVCfk>+0(9xKAtESSU3j=i{s{MD)Qtz0%4A+y zpnV4D>LvOQy`41ART=eCLg{OkIO{AP?+^XlY8wnpulb&54Mc7;#dK)B&#I?z#knR; zuv1v8YRYpq?cuDXNzWWcso{OTgx{#FXFwa!KJ?x>`0(K{l4r-%TOY8&q}P$`bt9kp zr*fE_7*g)X^VybgUtiyPY^1pE8fKEgU43jCpS^1@4*d8?$ACcYbW85wPu}I>3GbPi zJl!jWTGsM1zT&HgS7%$jJJ4?PJXI~50WG}KB?f=Fbvz0S=)!#MmqpasF`W=quqd*J zjq1Kb8SLf*Hld1PQ%?p)ndx8=xF$PKV4MOkYKMWG0}vBa zxe^(Kv)ck|WrpA1-JB-lx$@80s6^u!Si2rhY=WR3QQ4k*uTsa;sQZh7mMdsHtKS z8XEbFhk)QQ2;9x>zR2eRgbWK3y_gP_7CD`Iy(D!E2fa1h8ZY2n0}v!#CR#}ePJ35B zOz&j~m&WVdxy*u-s^b;`e85!edq+pw!O7{-x(Mt{m@eGWGTdqaSKGhKZcE5>8#WQx zY5sw^=d~{PUov?f*aRealqA=s$(-qR`|?b>ucSY7v^~XB{oU)b-4McBoauy>j@vY}3+X1zoowA^zHbSR^`2F7DpuN@1r`myp+SnMCpx>fbl$mht>ihkV$4BRo8O zdS9NA1TP@p!Riq%({vrWfB{jfQnB$$X;6+Cdobezzb(_Chc6=NLn=K&-t~Qb&`sKx z117V(h0O5B?&ED=Q1>!A^Er*gEj^qVz-_t*s-`%Rr~RkQdxfBXe;Bq7kT}u*ku%V; zJ6S;BVNigsX9SIl(jf)2@ESC41>Q&s{ zcuqvsj;a-xiw?g4YJZ7{XnnYF9)=`8fbT7PCz>s-!=oc2s{2KN&BIh8S+q-9te`oP z1PM4SYz&*C6eD9%D7GSAnc*K+h4#m| zo=-inYZ1g7&CJa7$VelAc|6SrWqde~q7iV)>8b~W=tbgfehFZcxe{#XIa?ZC!p1ph zp}`3mo=1Bil?b@+F$ZH{7^{9w9I9hwxDeA|wd!#2uPKOLli*5LH`!h4=N<*v85$T! z{Qr=Lb1kLhQ+Y{P|@`ygkWyCg;;INn?h&?1MawiHFJdpz_XH2 zF|Er4!(X({JX`v!10_VH%?I9HO$pssvRPu#S&WJH8qEbl733(vLkd3xWi1lPIfx5RW!O-*W*+-P7Cu)?x%Bq7VwqBJMR zYN;I+KA3t%OeaSl-jc1J-=q9Sz6Esz5}I=Ra?)Zr zVAW26^(ro_A$GS?xP9y&5=THKr(5d<`)d)VpG_pIE>H(Ce_A+LWe#V604%PT&nM6& zxI6T=U5Bb$L58N7!Pjmf%U=R} z{yX#>u1WQHKy0}%kXKQ+ z?%`~j54e&F7X~g+qg%-hgb}8CgW_?@);AQmm#)kPbP_Q5gxgUwi+8$GVwFYrqptgNhjmFpR#06g&eBi8A$mFdYY@bLXn*Iv^$Z!nD0NsKa?H26kz1^e@nnwVZv zlWF=h^TGO`X9igJcr@gP9or-OBtU5Qd>Q$L-IsUf<+_b>-bqswDg~dvNf=v)1z{?_ zc#AEImyns_B*VUcp8JZ^1s7fYI4m14aV(d5B)F_uJrg@g#-EW(#P|VpLVru?QQc-R zpn?*^MhlEv#r!Xk6ZnC&BeLDbRSGQt*wY{YdvrXtb#Gd5Wo0FMh|zBK`Izz5!(+Tk zp|9F>=M8W(HEm;-J40t;us24*Fy^-lw_5{%2y96E;V_TT*t=i(I)nZrz^U_gCvDs0 z74&s~bzQh&VCuau&pr2TA&Iph)_@9S*e2iCSOMOYiK(|b@@ZR89~~Mb z1~J6%pR)C$h45rYs;24XFYTPHBo#*Gr_#kgc>+hCH=6ecH02Hi1!zrJ6{z}V#vLY# z_>-in#opEDk({T~uGUOsXYgv_K`G`~yii^4v*E3{XH)!>H}WQ3iN|``H2Cvuf|z3; z2!B6BTSotq4>qpSH&4LnRn^rX9r+VdI)QW{bhJ_k;AnSaV_~vZLN6{DpK`fRqt zEo-=MyHH-OQdzELi65`{HaojjZ5TYPxH6g**vr4o@Nl(QHb7q_XNd1>lxy`CS`+UF zX|oCWi)T#9Y?0Q~DuQT5Qk`bz65c+m`t1O`zlq=?@vc2Pbq^@RND+gelEOeD({u4TT>&;CDteA5>Ym-L z6W3AeQYs<0FdFd+G|tpqC{f)Y(o3mwYBk`{x@9i$M(SKi8j7^FVqq_s*Tsqxz1Fuz zrTe9*aINe1_)iAXR*^JG)m}Y%{gE#{)+ODhmEFkFua?kTV}Y5 zkD9~T7C%jNrT18e?A3h>n5@Xu(aX{3`LZ=cFrN5h?rl}O$dwkG?6Kp*zAf~n(}Kev zo!4YNDEeo!LJ8nvyh27v^a{*}gYfU*sZjpF%( z%7WDQ9q0*NH@@P^CkQeG(MA{IOeK{)*tc8vy>w`L+84Q%RujgBGpoUKco=uF(Is-u`aw17tyx{=tQh8DD!>6ko)yY@YpyKCtQtBRl-bPu$LOIC=Obt%OtlR}*TQ zIB})8fUY)5pjfrLjZfONRI$S*H+s9mX3esqJb!7f=A}i)ZkEl2Wrb_LT8De8Rgd!T zo{|AQCAr&WWr!o7hQ#}ab?LZ-U8!s=FC4MH@apGuV<5~4YC_lfcxQ_f3CZO+Rz$OZ z9M$M)7ulJ!#drMl3&}U~E(kf>*Y(mJiHJK|OmU@O5a~RSN(h~Mf7FSB~OseDgwpWj%tradQ z+Rk@cD6^lM&t~cQ#hE2);{qA|^N%_iRaI3dr#(MHO8On8t~(f0pe39M%0^jP85Buv ziY-v~I-~@%x}8ZgP~z`>%RV7s}ca z0A2Wmoe%$t=B)V(H%^{^&zo$P-__kZ9GGU_jVNgo=#e6v1gN zY3p;26^I-0{niH^YLnC}+}-)T=TRXu)r$0Cw5ms&!c@(Nd)L=_h8)K;;<3HPP8~aa zvqyqA(=9|gDEmFKXU?BF`|G;?Y-Pb8MDdunmNO=I<^E;HPc7l$0t*s@* z$IFWCxCy1|pDI$`(?(nh?pWdQI9fg-a*U-Ivz9g({DB1q`1`EUU*4f~IK`;x?ZYfp z+K|1tsR84Sb$sFIxqe3_g~1;~v$1xLD2g7Nq9Z1KYptqw<%KSc6MyO41`1^6y_I1u zi@prDnuBS;xQQq-R-I2ifL*A;L4teo4+E2yTluj!EKe>u#IH;`epqiN_Ef<9*7;2z z8u_hL`=24G5+amCQg6yfD&)jwt9oFGWBkgntByf&Q-+Dk_+4BN?kzn0x>&Yhr$%CU z*C%3#YtWzpjn-m>CfUy}l)P4tWo1`;(>uqVC&xipffJ?5Tu;ycxa|FVS?BE@j;HHV zRN3rBHK640O*qzXhBnizayayct+CDv1QTWsT<;IU`)v(iontrTrT|@e3t0996q)EA zk9Lg*|D6f2eK3;|<4i~fQ-nuF(Ff6zpJN9zKWuB2(`U6$zio8CH3W4xVS)S9rF65o zLiYaf)1kFQROjfV@k<&~o}*=rj5)>MHBJCE9xum-JUs^E@c@qYpU%bFy6vs}z%_u& z58R3}G8RCOOA{F_GN1YR^K0lWIQ#)m3QBrD>Z+pf`8{1 zEAer5@EeP3e_2lmtSGQt06nC?IRYmQ7YB!m-~L-1Qkz+;=#4EqKvwj2g8Q1?ix)Hg zkNpurqKP*?waq)8RLED4Ayb!!8ia;-JLh&<$&R+oLb0;R9An*f4S$L*EGa)2cE!l` zm;{CiJ%WgW`^~L2%w6vwQ9kX52OI-u-7#}@J@m%HUg*T7cA5J2miUCgBvj%$@`E z6-!iy0tFSWFVJ^iEe6AEwdQDT89+y>=D(h$@*L*sn3U<4;B~FM9w4)v7GUW_k z9j?oW@iW6ZTogoG!5kEXMJ( zuL(~p{R=3C3wN|F5ot@j-MOvbJx|=&#>_9(-Mcsz_jhq_=qpHIs1A@XJy33e(@ev~ zRqjC3E?l}%hCDmX$ze`Z6vAxcj5%%QZ*m)}Kkc2SVti*pJnk>{Ns`#nl{sd<_o?iD z;P!?hNMyvbZpn9Cx1;+F4J|ew>hB+*qib%?Ag)G46v_n3n2byip}KG&rkkj2=XT5t zx1(zHs$37&)-knys3L&o-+VN`M1w6nVV$Hs4<3FDrNfQ#QY0$XQY7J*b&a#bu6fc* z;cwtY{wIR#!YiMGHMoC?*LtLHih~sEy00kDljW)67Z>v5g`T&+ zyh)iBIx^`z9lQr}Ix`0bQ~guZRSda>9L`ipxaC!=q~B zf!G6tM5027N_>-_lXJPkDESmueA8=H%ODGhe;Q1pvzv%Wt!?c|#JLUR={8F0 zws*PDKaks3c<-}#&hIfrpjm2*>B`c|1jGiSwTspq08$I%8 z(7-3m+iYe%Zy?F?&oBL9RAE0W4Q4it$HrG!J;9ASYzPPZQsybL9^D%$wyeMRfkzIJ zW=NWx$tb8ia6$b`vzgdv3@SI-!x?M%(Yx?{F%oJ(2=^rMMxE_g{Z|w^>s#nXXr10&Hj2!h%DP#2T$0R^`z^=2KCpS<5w!tkGIl3n#aneZi2u)&=0iQHsTXG+ z58T7LcPIVXM996!b`IP;6WnhR*BBk{S7$2O_NY-5hu=l(CUa#uO328Uh0X7@3%S?QUDfA#@UXnoPs!j3UUg z8F&4(F6j)le+ejdd-e9LP{#WE_rXVL83p(}$M5`J!YJZ*x``05_2ko+Q+A*>tA1l?B%IAEkJ+&|DldCe75`J%J97F;N+0wh zS5~Zo(LXy3DdTHaPXa**DIpl>u2FVQ3%p^Kl%8hh9+q1*v)BbiS~!Nt3_fa zAcFtekaFoD;S3)YAJ)}J>U%BD&(j6bYR=$J*#|%UQS9(HhF$~3s{2P>2z(m=VruY=XwYauY{i>&FzP=&Il`>gF(N02 zT}Fq7-`??&*~@7ym!EIKub^c1;wx#_l%9W0`~OW)Jz;(x7|PU*@Dq*B}` z?V70|4~IucNa`m&aDWx1o|EOU-7@ZWe|+2ReOA2zw<=C!IQ4ehMOl>yO9D~a|M4!D z(|Q;EE>Vpni3egFwcno%`Vqs3APZZPJDSbCAGcD$_O)F=UDKvZEk@}@zq%NHuRYUi z3C{nybMw2+@bK{L;rO|r!@3-Px9s4bHnw-d1Y=G&sV;02O% znRLldKmriF(H&4*H}}6{5^x}1(*Cv2rK=Y+Bs<=T-$co6FY}3HY@c6Wk9?gro4I%s z_5CV^{el+nq!Qe-L?31dM+K9wVQiLr*yjx-a@7DQF4z_)jK*dD_$2zClCh4md0@oy_>)Q+q*Ka8A1gKF{}`dt60DWoKnr`Edo`CirjQ z2rFc&;6Xz>z|zo6L=|vY)K`3esT8kes&7*2w)3+@8rMDAh%wWB7``g? z?-~T;U+T8I1e);@fc#T1joq~mAkDNmpP`t13BGWoqr)7eKod;k-P(s<)K2ox4|?-h z$%NEGjYjJ9sd?&$^6~N!!`k`Sj`2=g*(JYLk2*Mg`dy z(OxvB-gCKaggy?~KHT^o{@bEH7e z#lgYh;NSqmfs~Xq%wJrB%Z(d&eXiQ*bD6FKtRbW6dgAbHrzTI{_WWj*+BId5O1Z7W zXA%!erG)j{FCCm{NV^GF-i(m>?}jI1Y<$PK55wM};^DvgF3K?J*`@w$Tq0_4Hl9(6 zTFu}Zi2Ac~m}l`cD@o+MxxQoh{geJHTxJ65V#a6lhe1Vx#b4=X-V^>;ZELVUpQ^hy zr!C~}=7#smhMtubL%9~ZF%a@!m2(?GM(^N@r~4(VhppmQnR>_5eJj58G4G+CJ{?D! zFC{vLmq&j5XA7KKfuGDtPH-Vyx>0frgG6Doaeq@%Q@eJyfQe!N8O1lml(es&zdn^I{f{ZoLG3LMmKda2z*X6YB(0$MSmjzp zHbgABzx4oMz(hXXc6@NDpTBrP+!U6>f&JXPTsT{SI40wIF&h1C_dSQX@v!(v+c{Y^ zpSy($A3tb+nROWP#`&6lvi16k(SH`{^F4&1MIXI%$<^wUy-~QssXpohj}{^=nRI_6 ziPZnY+}fKXHz$KhSD)H7ISnuNACCIc^-VeF097l~gx zSoTvnk8O^<=xmSSVdCLYy{=p!fnS%Fl|@LWR4KEu$SZX(=FlpJrcoFDV=!IWvr>NZ z#MRa10RgKI07G85u=(%$HYKVPa9&%PtVOO*RG)35&Nd#Ff|CXEEHHzU8lL6eba?=A z=HCJ5%*@O{nwmY$DEPW}d37YGKl3H>0{WC@l?WH4)-_%a@q^CRhKz-Mnn=HPH0@rH zzTTGtrR$P_d;f1wfoGCPS65eeqC_V1bOrG0O2?f>Kiqi&DK_&@>CyiFxF;z$4-XIP z@9p-vL_Scr+axzu7sZo5nv>O`;qiPDWcoGjliuZ<;1XzD z7j#j4TzmM?a~{W&2LnQW*8Kl~5S$$oR7_08i}?=F959I@2oJk~syV^y-n`PzH+c-< z5Lj&AE5E@>F1NLC_+F!o)7kt);W^>>r9sCJ^6Sx9`fUUY_d1S7T^~|kY5bqq0SA0| zh#z)>Q7T3xQnlGqKF&n)7-y0l+502{u5L81$v74UuOp1(yaY7`xfjB!l z*`J(*B&@eL;xE|1#I!>^AP$(}{fF!hw*K6EaojG7Z(wVfYtEY2R&iwK>;I9p?>qKe zyZFDjjT{6Ch|^-G**fyfJzXw-0(s^syyLVw;@FcS1&T!4_2cifp^%QJWnq#1Yfdv3 zd(F{sz-T7RhARTrYQnm0y}BlzM8DOX^&c;+jVE(x{L@iI}2ksQeMT{f?kSeQ%K%w)vBU$m=pdgZ__c?l4QJ44$ni!McQn_x_F7MoB z^m_9sMtY`Wd+{UhBdXo+iV@5Cex#SQu8$$Tx;DbEU|+kiA5H1Pa2}SkKO5$H$V`<$ z@b(&KVw2cRJ5g}{?9DJ@wZcE@>Ls+7_XK?i)06bGgns?{H&R0!ua-_KO?v`hsIRXN zp%!Yvohr5*>`A`+(R^d->&`s91!qjCm}D^p*8s1AfuA%|3@=$p)$J7nf912Y*_=D@ zrL!B-6_-^MK6Ob6{>c%YHO`T#pGf5R0dm4=)EY9P zSDN0-xFI^26>LQ!YaEbCwhmHnv+O5lbJ$FAYEB9Ta6+#z(XyVOwPXqwRlcZX&s3F9 z7HK&|ALj3z(D{kEdPYaWz{)&&q^|Tm_POVUOimWgNuA%ruN1Ae4^!{Y=6LT*x;Z4& zJ;S~;Ve`?`OY2&Rf%@?n)fcgA{tl?=z_zJh#`9QFS8ZW{C#y@s^4xQFADizzVOZXx zWU*cnN;@Fby>Z`Ed*x-kU4s|?`r@=aHjAY5+&@3`FOHz_4U=1Uoc?wZuMXy7?PBL8 zs{1+Gr6JRnaLQq^^9W>6(7S+|N)gpzaonL*^UD|h5SNf&laLfwbu%F37kw~+*7f7v zmAXcEV(FG4Q#VWFtY>uZcz>u(TRAOEu{!)T&Ut=Qcs+Dufs5|>dTQGc1k#-Da2FD}cIV|($uUY!0@zA&I4vOLv z>9vMDR#Tkb4Wid_7c`#v(N}E4P)lQ2du+$+c~l-Go8IH3-}bnJflT5f7HolR5EJMT zY(u1xamo)Z**P_53-~Lx&tJPue_dvjB>qEo+E1)6cWRHXO<<`9-}fALMWn5kFF!*4 zpiB}a4lf#SO36&K2}lcj!f}v`$~!6JW)}YF=dN~TOH%HyCd+<4)jz z;U~HdnFt=Mq0d0%a`md=n+N(ST}*DEQ>DwsM#RK0f(y5USfNzqG*kRu)ZR^^S*w3l zTGAwN@3s+{Im3R(5CxaL>La;sS?uT1a*Zjw@8No)>AA=SH5xgaR^elbuT9Mb6Er`& zTN~G<>1|&6q6^zb{r2a%jt9$A>05V;u6Cm5_Y=sw=H@M5c@YZF#+tILH5!M>4qq2% zDjGM-w@6No%UuyfY)6;0o?lPJz51kGy7b)ojpH|L(`FP@Heq(Ns}dV(A$o;1Ey z*_1f4(D83s%Pl?KsM2nx3Bsh1qY;D=%)wy?$&g&FVz(90C^4Za*z}Nj7jtnbBWyn3 zgZQ=~VtrvRC;1_i3rB715~;#kb&kI3Kw-Mw&02$JJM;vqNvs zM=qV-`#513Ln9u;W^%sk{9)2$rR>(Blh-@RNALgI<%Ws~FJwxy!r;5WhdAMG$`*NU z6Cq{t>4z+1RPwRJI8!1WDaDjC;;PnS#i?j-N^`lzV%`*D&U5JfWH) z<2s&Sd(+C=b3ItKS?~qA4S$8(XMD73r0m~68ON=<2BPdNEG%?%{$A<(r@(AsXTu=| zOg#Wn3dG>_+wTI?w~ykd?cKE zV+}|dnL^#>a`SXCPtR0*1!U-ZeQXLQkJjRyJh7Ze(I|_x5cPQqp0k2Y(77ByVR&tUM{ZYM)Kh z#WHw3g6eTxB`vFvd=pnSADr~?fVJK8d<^Yghk%hiW@sQUms{_Tw&l`4-xPeyojeM- z)$E@vXg88ut`0+5g{t`r(;iZX!fk|a(7sK28W^A zTU&`^2K=Ez5UX-}h<`g}(!B6u?=v~hl(_pTQbL$)GU?5zc^n^3 zU+NL@AzNSvV;}6Po#?>HBU)c~oG$_dvKzdUMhk2+(wXvikM^RbM+vpXx$@_(dRIo( z5=SkSzIq#g=j2=}Dn2{FwTP14cDH?f+52#b_ec5>$t1P%$wGSg&J*Gk7F5ZZMFUmZ z-#-Okg|Q#N5-7#a;LhXid)jnA!$kYsFaigFvo5jbP?~g;`aduePBD`)l7$2C;Oae; z9Zyjg$Ug4(vMryAlNcu}wKAsaR$yqlK&-Hf;>K&o_p$P6R8eykH}@q zp|yqke>88w=(%m5NbHu=!xUwe+UE&AI}g4U7GWk7`Wc_hg*+@T>2cuw^K5(Z)Y_{? zLYvL{BUO_tp&m)Wo=b~w-MP*;Nx-I#+KG}_s^*%cR?XMV1nOs!yDz!VnEgHYf_@ln z?d+`dXOsC(U?v|XW^#zAde(>+Eb#)^1jYh@+$;@`h03#MNf0iAjwot!*cYt0+U&L) zwZvy7ez3rEg;uQq1PPCY4_%JU%A|Ao8JQpRdWsHJ7^&K;Oi%x~LAB~RH{DD|wXqvd zws1v}9wY2M=gMUz_FVXWx=nuSpJ$xSb0N4RO7#=N;PowOhdg&;;pmlyNgQ-<;qvwwN-yHJTj{MbpFMX6!$mmI#hjOch?5k_?C$#phiL_U9vhwz`s9={CO zG(8&Yp#~3QY=6WAH?pL~O7g^ZB8avZcz>7<`VttI>nWSdv@x@>WHZzEpHv7WP^Y$k z-~~shhSyb0#VCN2R-ZWL)4k_+mRqB-a*<^tHbrEsSNejkaw3oTMjkQ9h3ZrC8!3LT zY#2WzjO8^P8DTK*B~~;sL5A#b+pb!n`}`)W4w=r(@9|vQ$KN9%9#U3`-M$MSKZigD z#{MQaI@jCuV+Reh z*zN>_U?UPeJ?dCMlo|-AH<09hVx}sIZp51WCtBY7-pjnvvOZfE^j9U^y*PT$Ea=a% zXSsD>=(YZ^9v0lqVa{ls+P&@8J*R9A610S}szZ!L)4=unW$CYW87PxkaY$d^VIW%z zNX`x9?dweYa+d2MJbt!3?4xq*Y}3s)*>>^RO>Qc(@SGV}TJl*3an(@=VbaD%BLR}) z{RI$o)2n0&H43A1qOH}{k}1k#!~vPCo?J5<$926!WHo_ceV?p3RzFSGos3Q z{O*gjWYEZOXu1pD;ix>0Qt$ZYsECauG&$plo0A-8Yt$W~81}nAUVEgXFI^91(UcMn zge7q`g5om^GKR*wsjqoUG2(=OycRcD;4S+3p18wt%|>Gzq6RnZW|6)^w~4&+r!Ol@ zFD?FAeXz_RUnD*zN$vUz4t;`Y%rQTCEi7PB@&2WQ&1Ra#PK@ghIuY2^&a#o>uYz&EbC@C&_~XUF31#L&07BR2wGjk3bmi5$=OiV!m&cX*wF2r?-PsDCh^1Qx zB>nktGfK_D#0UM^eJ{UqX5fbGgq`rrYiAn+oy_^&XLwgZ9ra8Kp#2c`^etZPLeHVV z%t(j<-Pda?0i$3E^)0;-et~UzdPOW5h2NH*BHQkfByD07e9{#swITb&NpFwFTOf2F zaER#{&ckLw`@Z`#yCipZlXU6OJtbL6g@H$%2eVMGkI5Yyf2I^w1>oi0uXBqL|k|*zqM>+QYJG9`qMFFR^18&=9ffp z{PlaS2SvI0KX#lLR+&6Xn8Xg{GW`}#0RvQxhLi-vIyVXZh-l=H6JAHFpuElcKc*8x zDAy2%m`ZtvM1z`=hMmcE>I#a_B7Hzegpz%qTXIS#qSr34KnJCq)6UQ{ckN7b$5byf zBthKv^j;i@pRu}*33YOvdvSB6b7s=!nyK9y#q0KJ{$KR+kx0jlq|4GJByVRpTf79K z?s-1pKG96+3%4ZTer=z>JaK^UbBL3ApRwf&hz@o0Y6aVdexe(uPI>0ZE*)8y03iyY3`%i^M;VH9KMzAgYU}`-~X# z3|P-zD&Z6d-#|Y0eu9hm=z~$%j{%VyYi>4$L;G{bqt6bvW_QdWcLn+%5K2&z|H^_u zk)`w)FfwElyj5SR4l7=lR&&T|QBf5a_bJh6{P4CbkCntu)j}*$G*UgOmCv6TEfubw z#G?<*(NAOVCL7fMjKm*Ju0w29$ALD>=;Tf-KlKLAHUbX;!PyUzar~* zBN^yQDNTb-C$Hxd2aTkcC8OU~0$c@V?g#Iutu9snT%%PNzuo23VF%3Y6`8LByB;(_ zA^qwZ4+-EilPs5nW@95iWKSUPY{Q_p^*}lxF)(f3^$Px~|E6joJ&G(JK#k>y<*mT|=*X!jR)s@=^!S6uyQ& zY4EG?>q+L$6dNo6HaFsxQ|&>^@i#G)$nKYe6RnsO4d#GdE&#oWD%WQSRu-0VP;%hP z18HPWCuGUMYPl-}Myl#&^lYqrYOoGDM#lmLyIJVPp?`?$Y?q?ed!-&z@8Gnop`C)& zc}^3nxAC7@?YEjTVCI0$la#bGU2x~_Z67MKmh*5)11ZicOof!0_AX_2QFw_&z}}}h zvl}Z#+NE>5GemjvIVOrq5|vwp++RkKMQ6no-P}6Figc?ups9)p*UbIi2vd zohGk_t1Z5w*D}%f3%N1Z+2wN^hu$6|^+=HznOf9d>yuXHP~8_Nz~Z}`YR~M|q@oj? z{JYMJKd!n}`$*lKy<3>`spl`rWFZEWx^}=_hJR+z)X;49oi$R?qtfmp1|tG8k@YY(=rBAs-t8GtVjl;=9KJ(PpD}uUp;AxFg*IN zd+}tCzG6Ibby57nK<=-b0id=pl#`NR^Cmn#0ZTrw_JRS0B|0LC3|ALzp*Ho^a^=@& z%5%?O&{v9Juk8LXn$&luyRqFi?7`irg>h!tK2$^J}BbK-lr5I-Gq_7COoWK+o z!Ta&!x8Pha-wSM*l8vd6n^*@{5A;;BmH^b}dpH|^Yk33Q-t|sJR8ax`LmX4=EqU}W z>Z*w9rN^9(D^yKip0IyAw5|VJeq;XRa37A*1LTMbt>J;Ql-h78go!!F*&&SDTkI+c z3ALBbwURZLmZjt$4)?_UC6C2giSAy~zmlMu@$;-mE|;eQgi!2CyIOrzp^!}mQ{{SI z>s%E7sA263!o)@=V337$IZWK2E*A z;NGXPv`p+I+6K7WrLtZ4nZx2R#ces&jNmQv@M&LaKo9! z)xKl950g;`|)ESpS_ExptiZ z6F~yzCk%3)=&#}pNZi2rN`8o@NElX&2r&Uz$iwJJmlC8^!Y^vhf=tFN67@}_Xyw7J zlV}zeL^dNqFR%F7mbQz_2hP$*l}nF%h@Oa^Bt5YYEQT;n5677CHfb(q6s7e)Wj?w; z87kqf$ou(*+{2gDXtSbuciJK6=nyDmEsduhcaO$skr$#0EG^`GWBKsw@(HRrmqUkw zJlf4sV^Bq2Fsil86n#WX4@m^DtB#(TD5;ena@F00P)@f7q?W3A!*Hf(sjY66ov+YT z0fK+LH)g|q?f9FOgSJ8gaNUnMKIhRvcU+fi7; zl<&w;io6C-H)2KflKlbVF|+j2Y#YtgoNV);r5ewD_S89UBm39L{$cz3n(16)J(9RPRL)$zR-i$EBJXPG>TG$3sd zqO~$tL0rYq3m&?jQd+3ADx|zLKQo;^dyaKRKIMh~Qjf;XacAj`cT^8FYNnew6Ym=t zi+y;%ubK{sN1i|g zbL#z|Z7b^Yc`?K|BaX<;FUmszWjT%iR<|SVF zAw8l1QfGIQJW^>V)rMcVFz5q|_39l)q1m6obV?v}S5c@%X?`NFqEBg;Ujr3NX5un= zL`urd$d~x=MLHBo%Dh!*YTr00C`ezYkN;Y4I>I`w+>)Un(R}uW3)On^n){Fq0o`7} zW#iA25AY2kK@;Ldtb3f|{A>Hm;2hZ8i>{bgx=Y0mMf^lk5du)kOP^4US<=8JEx;#u z8+LK|7DIH`y6N#2x{VN%IdIgU&QKk!jiGwrum9U>5qR!My*>_u;xp-$wVx}DW+U$+ zR^`V$7*}6AudO#Lnf1CkN8zRpgDp)@@Xa_((f2mJ8Gc+X?OGr?7DRm#_S$2sZZ3gC z*f*DhJbhjK9wTND@5(ofBL*=a7-jg@TI+%$cG_Q zz01feua|3HXONIuSNnXq1AG<${``8tkx{`qFjU?Ind*d`AX&ELgZq6vMs+Wzu+P5;h}6<{+YiidX-MvZuHA10)(!oYJD{q>z{cBM&U#&+^u^F@f2;{{O@Tb2-+Afy*@e+( zi}z|qZ=;}lgX;5a@^#Uh;>5>et&O##w-%%%ZFUot} z(p{MmP*mh&^1oQ8+ot|Vd2+~^b#%|HFom#e=AP3`DHi@K^JS_s_NbCyfGELvb&g-? zb^JiYV@Sku>Cj2{^TxhG0Ku|V-)!r@6P&Xt&Sl( zMfP#>lQi3qMA{Nyl;c$|0>esUmf&=^(@GMpm-HXjk}g#OZ8M(R)z3w4%pRUT4h2I3vn(?w{ia~OaHtFQT6<%=;@jo9A_iF5Is9P z3zwltavy_JfscoWIb6DkRVHKjX=;g7(V>NJ9fSg(Y)#^lRGiRw-{?&!B|*oq7+yAO6{#wLL}FoL|(%2#na{c%q_ zN*vUDKjKVCp{*L7P7Omk1^0}C^C(nVT7A{SF5m6YIK@bUZ5DFNnqinUEE1bn4xE+kUgbJ(OQ=X^GN`mJGY_&r0GbhVV3 zMR>OcX?WKCrT5i`DkJsdn}#3`eP%D)CpZK9XGj0CptJi8A;$lXGGbCUATJTK92_2Q zqOeW@jW5Gj5ecf{J8iPLYWN@DfuYPBZToVTRIzGVv}x;=l;OZ5F9Eg#Xdsa7hO;`A z4#;Qb_SkcXu}^A}J*yEhXKu=nyICZdeG?CEal5^4tmvGEU;8`>4+Kdb#OG%KOOD1hXqTWdU$Ra7v zBp(p%K&K@PcX@|z{HbBxFg4Zxt-TerfitW!hNlYN-5t%5>U{e26J;=vW14DMsCoJr z+i!Pw+)5089hoIP*>CBk&3DmM}2dy!b28rrz=KMzDk8` z{cQ)XE1fF<)73kRSs;U|SToMemn zX?}hWBeo4AJRg$S9v6X3{?5I`X!B@!iQ&qhkJT82@81}zpvKnEm*8kvP|RB{l(q;+ z{RNyX1BPpVF_Ab1K?k?f$dJ9-E|Z9UZaz@jl+3XQRPX;$u7gVn2nYZ@NoRms3<5ok z6g%T>0XINF5Jw91JK`)X^8dO9?}XJG`kc%jKd%DB_*ZR+M93-&#I5V@Fl0R|QQ7p&XUc=?3{jY@_(BtRER($vt&n%q<(;#J6KsJrvHab~jo*Mto%#!vN&W z3O|cS$>;-85{CY_3=8IpMj8C(gk>1%x6xw`HnkmYbUysx&BJITPimL5vER^G`bwc> z#r87MjBoBit7wP%5!vux*OGr{M1r^Mp@D5LisZia>&=Dx(ZgW(AxPVzJQ=(4i<#`v zVVTnZ3==S8*)ur*d4a^vg)PPR~?zzuI`FZr$zQelckeeP|KA4J*juT?eX=GVz4OWUWIdo@6kd@PDaU z*+h2XFaOl-0dAc+%~y_)(LyWwPOPR^T|&M+S>{#4z@u!Tg|*0nfS7toJLjeJ=VKT3 zQW1;+Cl#Rth8XNEukjep9U>&RuAfAWD7TAlXrX{?Ie^Yk1 zrg)tY5YbMhq~movqU*kwG1tY?$o(sbhA1Eg`2i;qc}G#$BEWf~1vt-O*5KD^q^ zldXt+IWmD7GHCt#POGL3h>iwX<%pmDr?j60O}pc&I>Gj zEkX@dIn-?$vjQ{{zJXHlclT+D!<6-3Vue>29vGQT%<~}jTzbRM3c3$|G8G1tkb;@^o+10aQ*! zN_%Wm81L}mA2ts_!ts@J&a@st=V0X3%gH;FD|Qrh!N4~E>6w?@;&;0_3)@#lA?9G{ zKz{{Tr2xRiB`!YBE0%Y9Ha6l0f5nPPZ-~aMp12$O^&h110{|pWZ!3Y=TYG;cx9Enc zKOnx7x`v~QpROwVT~MfywqD!rnY=LVm?o*0Q9p4zhwIH;v}AWS<3~hO1!jYhYdVIf zhBW{DBI6=f(v~(PJ>!T)Wy?5C~#JG6wmbJYM`dCm$ZKRv7~dQM#vY`T;315 zn#7T;Z-!{qB6Bxm?+E$iRaw_tlukpwg6 zUCSgX&S5WslPU#m8Ia4-x)d*D(2_e@Fm6G5l)H5F>y>=#i*Xz@CFiMMzwgoC0X@l5 zp!m{|$OTB=XfF1^YXbyEvOhS(@E`wUR5Zwh7p#<4@Hs2hc`LzzakFNB9r^{Kvd^?~ zw`}-&^DdCh0vymzo5&6lDKx+*AK5I)PkXGPE`EvO#(!f~E+XaYsFkB}@6j@^`9VxW zLv*`7=~C4{1p87RI7Z79P;$L$wc7&0T*>0>kcowP;!rf(L_CvBD?jd9tRwXC5|C)x zr00PIdWUc6;e=$*F9~ExY3)42Yrd+fvTAYvxR*STSNibRFBBw!Y{=-f?#;|C!=^f{ z+h*fpx3OE-B<>N&-_<-r)4dJ=#00HX_1Ob|!kPM6CW}14$QAs@7JmP;I|-}`o9fZh zYFukd?fZ7SS^$CnfM9GC+HUtcare$l-ak(vGBHR45Yk_2LJZ-jsG`7gt>0- zJn7)twgB1O6{zz@mve#8(O`e2Ff>c5AOYh^Z*Igb*qA z=I*+M!UeP|I0~m;_s;Q_fVdYZk=EkvbbHFq)y{@>$RkkHiw_0NK7Y169>du4e}2}z z7KvxsRJX@*tcMsE-cIjPZfsOxIUCR5-THSeaPS7!VxfIf(^%&kA(tNjGQ1$sv75ZH ztoPFfxs9A{_b-OtA9_ z7{SOI_^p=?{ndDN0Q<+y_wWrjRSuPbek*G~;enIFF83JG->)GjJC7LXPO}R38x&k{ zQS|<6qhP>@5-51QGhpPX`)!-{FEs*IY)%LmLqrc=8n0Z3iIjOajvS=4&rR*5XTnx~ z=V}D2O~`xV%NbNjY{I4U$Wi^xh$nfI&x)SZj0AM|_1`a^w2_#w+ijn39=^X5{rB&U zQDG(D^sSjG+DH7lCk0x2wE}vnNO@0U;}^n|1?Nm!gy2*Jr%Wb?UxZjXd<`q%ljW=2 z;V~J}TJzi&CZToTg8Q#|gTsWu9!Vee5HxHxU3~e02||bb087Lx1w$V&NipOwR&3s5 zajfEWphm%r_RZ>6j_pU)q0({fq{I?l$uLaN_2>*+E_={wpAmeG^^huY;0H`>P6vDs zU&$)dh{6Vq8&`rK2?N%em|qq3YR3GoRc;oJ;XeoJta0UhqgC++p3!g}hW|C}n>@M% z0;m5DX2c2S2^&WMp2>pl{zGi8Xe1U9;o)tXYa~{~B6)w2K zuU{XBz`>=}K5ljWH1cEs?zVYTx&ZRpn>a%;F!26tcV=yMH8Lv7LpbxNIW8sm6IBx6 zea1s{*40e|v)#W4y&+TQL#&e5dMf%vnSqmYWME(y7@PnbH(-0s4#a*XCAgG`;MaJ- zuc2dML4hR-==TIJMr+{DHeSwljTUu4Iie{7FQvrcCO8#_v9MYWq_EIFcBDQ+MpnB9 zrB|ZXIhc9F!~~D9urLo#ZA%OJE3gE67$AIuVe*~_i;V#{p4Vt_ql^1XF2=hRA8WCK z1g^cqTzZ>;$HK?JR3Gn^=7w> z2u9Ka_dR!DX$LyvzJ2=!I>$3$W8VEjV2|`Hr1^PBK}(A$a4-NyY#Bms3`|T+U<`NR z1X?V3#Is){lpJFRIs$&zb7AG5Vut#Ky=xtJu)Pv$b>^buBH8i1z`>23p!&=1BO@;o+x0U>`}p!AE?CgR>n5`zZV? z6S6Ta?Kl?fBOE2wCmqt#`BbovXw!0ugCoLUO29sn=9S@s!^4RWgnbll9cc>t*1Kom zBb+h(?*FTQ$o}_-4}ac{;jxHv(iD1 zTab5jY(Z_o&~QR?dU-;Wm$%6KZFu-gjQ?7W*}gN()2~`G&?pgqQRk_p*~8;qHGIx! zWEX*Y{xOd2MXrY=Mg%QAhK#;a6^#~tOw?A@?kpmy`V356BHfj7p3(Au`#vjC_=eg_ zu{Pcgp|cXdYo*xe1?@i}e%IwI{=J>or}u9SWrWlw1V%sUQ0%6<%y2H0d=alJY=3jq ziHeMjV!O~!8zs6p?j%d#!{;&0Vn|$H(Kfsy8YY&N&0gn=`M{8j#B4~cq@fURaU0=K zLB;m$W{t?}A(#sM8bRepDJi)mw|R|>$boXJdHT%59XqX-=it$P01y2e2{Ca|dAZW7 zS70U+pIFk+;exl1LSoHKS)ed-HJ z4Z%J6qWG$@(qZ1P8(53qeSYY_6-FGadu$Lu@z%!^rG+SMw%v2oO4ZdTd^L2;e5UL0 zv}hc?WPgZ-rnzv|Gy z{YAs>Z+mOY+1))uz-jrx3)&7FuFxCsa1-?4)UA8vi;f=E)+UaE5+>xfv7%uQe0++E z!YOIM*jrR(7mItj+ms-)vs;!0E$r-Rp+OzHhm^YWO3Gsc@KSCY7vP5De)5!>xmYXiZR(>gsZGWedz&nR zSM%NR9!`MErSnR{fD{2|G($PJYt(=r`p|FWB; zC~<`rcy~1tbZ~fB`Cs=(Ay$slO(}Pm!dd4Zn7)g`VKZYE8IT**TAi={UaO7}>&ZE5 zq%xmDz(OhN*vf+^l`&|R6)>(+H{bjLd0r6}z1 zsYZu~qk?GiGX3D-ATBNrxJ0xCK|z}<=kfkX;CU^uQ`{-9U&h@9j3N~=u{Ib(Vn0{Y zez(vr^ET*iCa9ZKM&=Zi#0^dsIf~;*oUjT3$2xm+bE>=`l8AD0397T45&JpCkR(9Tt-Q0MnrN(VwMTK1%}~ac z-L`MAD3aM`u);RpK=UK_mimLSla!oCpaBcSVXFw2tFpNguUN2<;l;ACjsr2qZMs&9 z{)A>^R<7Ko&Z&#D^_Pk~T*N5hv6kZvgxlb@FLhh$3K!vx6cpcyO=oP9REaZAOPK0U z$-t9bdlZLa+)Y0!*k+;RGk4r${Iev2)1kk+|7{L@w3@$8M^boZU(rChQ3F1N!YoQN zM~=_IBJ^zA8d3LM`KXIKRkM_xDkCNn%d2wKkz%F9N^){iQfY@RbyJ^bY435Y_sWtG zs_f=J9<*V)4{>aZrqvJbgr=+BsIvPCzo1gY#u=OJ1P8_Z6AX;A^K(dD9p93bjZMT> zl>bdL^zH!Y^ng9MX@Hv>k32s=|1u!33sMZP>w|9U0g?TTZ_3z6BtNY867i4Z$#%1t z_|Kxq{r$s(?ZcjS+F|%{hIi}xpF^O&oUExymDijevTniY_DD&Qa;ckMwy+7zPy#88u;4d!yzCGZ$h;@>ql2-o_7@y0&s^Y(Ucjj5&ZrYxn30+z{+Gg)6EGcUH zZfGC%h(LGCRooqKI&V<SDd&sg-p}~aU_WZ04 zUc%wi(yo)*a*mU#JP_lJo-9NrSwFpB#ol)6C7<2BLPDP7z87@;3N{xdNqv0WT}`=X zbC7S>A=!g|IwXrBT2EHvn?~nPDe)*sQ%n7v&$8#t)q=i6hbx13rWeFVF#@YZa-l-z zfWvNB>hlv;cafcTa=y>K6hEkgHk%*mnJ^81un%W)mx7mJrFwt4pJ@@u?Yz%Fb2W=3 z$%*XTKBKk8X^YL%A^0w$;H^O3ImMrQ(vL6N&;0^?=xm)#eO@a0k#z1{y?%ptqcsEK z=HX?J@Qsk4n0Rft1m$PD%|zcb`#;D}7?8=so_zeia=ED;)iOV+5tbMIRv4#>1=3Yt z*k0=Ng|U$qKfMf+9w2UxcN`>Vl&*KJgcCfbY;T?7Zy5No)!vAVVl#5cqg_2cJe<-5 zV3Yjpj*gC?>%t&OxO>oJ;0=)QTlhJ)kB{1?r>5u`7yvr3w6O5-jB(A=O^fWI2-d2o z3Uv82vC#KSAotuLYSu%{HC83r$G{Isl3|>)lelISoLxZ<_ooQ85*!eJ%ep1Dr-r?W z10MV9s2%(fR*IR+k}J}bKNP5zuBQSkk&@WE`y@AxdOdyoTNZscBu^wvP6{+fDyuUC z27`RG-?E-BFHhjcnMFOaHhq}K9oT#=)Nk)`2bU^T&dtplw=+Y5Cp_ zJp-sfl!)4R;=~d(f{oE>p~ZkQoBT+xZDw~MAj_!9Y7wF+c>i(gWOkcj(`$NmsHY-D z=i$n5F+L`XP-f(CcgIdjQp&n(l)c(la~clZL!Ce6t7GYvv{C=fn5^k{*X~a{>du4a zR{r#8rd*`xieLQPhiZ3G+3l3M$T!vu?jW5m(eLZZIibt`U_0O9pSjSQZFwKlcWrk? zuKUjN@ced?V_ouf){o*ZbqZ`*LaXg%RWz+$U(kP&j8UxPB*d4d=(GnmqHWAghN(_w8CnZk`$DZge|FyErAtf6-haH^snnPy(aPsdN|rQ z3i92oXGEOI*N{mP*rfYjQ4=6Th>EDG)015#FX+M1g};r&df^w4s!FjmzOQ82YlF<4P z5|N|;O*;)y<`aFpROe&e-v=S$=$!8z&qu1!hTR%)t54Ks`8Ox+4z}wZ<)~)Yj+kra z0uL)!6%ZGE)8Y-{!vq8eWj?iVAJV9Y5j9w7_V%P)=go;MiAz>Nx6glNeh!IYCc+yC z@w=h&rgrc$VpLmIeIN)o@5?6Ygi!xX@1_vjsYhtUp=cq8w0ds#>DYjD1Sk`zKG;^Z0j9X#;XblxBARFZI^F3xiEP(2GxvjtUI+5 zQGbPa2r#Fy3Ot)NS}=6sxz_tOH+fY4=B?d9H>Ip!+%NEJp#c;}ddV7iGaz!cQRA95 z;zI~R4ih}@OlJviBTYA4!1V>(hSnY`&y+L#LUK+)GG*Y#=9)= zZ{Mo6uUk_#QN>w>Aq#j+)Baww|32267PAa=^D%#D1$bBNY74QhI5?b!!ER1LsPgOC zKhH{TPo(#%rl(7+wK zKcMpIV?whu@M5j!=abLS>5A!{^GC_z}1BQA92J)dZJNZ zKEEb>FRi#$v|zA^wj07UL$8?R0O?PR>~BpfeEb zh67kI#%YpFP3nYEvozN<wcN1hwYW!nI?z8oJlTn)6Qx*h9nH0uWpKJTQL}9ipuG%{rM# z_0$`pKfAYdk*m`1d9&R5`3D&}^!ad?@%c}x5O)^qebRNfj99o9wWo!lxH7)Ayu#&U?i2x0Nj4=?GG6?88&Hm za<#={O+z1fRBmK*GA>@pxis!*`X`EvN7XWY_r_@X=Er)K!^ccM42r2~v_b*Fw#M0G zlHK<_AyNR*-!`cqveklPp{2?El(&^-!}m93{`3&X3Hck&yfxI04VPYNI9YKbc){G4 z{HCZKrd^4MRks|Ytx{4UT-|NE6i{vZqbIq7fl2H&oGySKs5S$At`hZf`x`MO$&R+6*`M!{e<1O5^A}A*ZZ*Z7?Mu#W|bAaqgV@vuD&tQINcxf z=VJ@(0&FAB4-N(vH;;8g6aB=^CqhSuUY39ZDRHT6){3jM$_X+>8LKiq-)fugj_IQ| z8B3Mcyfg2o7e!WUFz&P%J|DHNHandcI8crLXn2V-JGU6s{$=CrnfM4NzesC{XBNhC zuu;63Dq7Qrr2y9(0gzllqW=Wby5sTD&Cy=vafLgWimCDToYXeAOLel@4a+39l2+T* zo;1HK=_|bpiEI}0I)zYUDe!cJe+79*_|_J;>E`;M{gh6~zUPB^-bDJQlmG#LzM1-O zc<+y{aG1kh(h2$w0%YvPwn=vuCUE-+BTjBN#_0?GPtY;n&t{HJL2jBxb2HHiJL2V|6H;?5IVO);!w~#KWG)k>|R!4)JfhI1445;0$x<6`Giyu+R2~ zhxd+Oe{%CXDmU=kcY%d*+WT2>h!b~AeBX^P#R;x&YCDW`EpBYSe+!p!I65~oJpm=r z5jBNNUPAJHeopl^@%Y)J6`2hlaAHIR0Nx26FfiLzlW_jmr#901_?Ia`S41vETRS_z z`bkUS7vZi1a7YA}wf?n9S=$~Kb<66NM59`%k(e!m*WtYjd*z00X40C?z3NHH zNe4?}nz5TK3ibzHwrol&QY3KF(9`N8ZOn%fHgqLlTBys=q0@NNaVs*=|u&@X-0 zf8cTr1c{Fnd_#+TvOgmGDU0BZ#gL@-Wf;xrDPCJ0iHO-liOKI)eKql9OKkfz5VQ7_i;z} zFi zz%D#1D<(+5ewO-cqEXPD2Ne~Sv_)cJ1Wy~>9(_CdX*LMLXiKf}5Z^v_ zajd^#eA#H~I?Foo#Kx*%NI_&tLa{u$V>uIS&9IxWnBo*N5rUg?2UdY7F$iEoJ}Nuu zFlG!Fzbdk4PT&khjocR-61&Un8@Ul}*EZ)uq``JUPhw0&qA^;E5%Oj{A3f^O=vzLv z3!#YLl034L_xku2fj251AqWMvHetiMFZ^)&+A3hw4CZPTQ8_`Kp)JS z-@OV|Nw$#HQ1nrN=f`ott17pf_xMIm(WOb_6SF>a)wiFF#)z|={4=Z?@TR%+Mvsgq zWAno%S@^XccVtr=(vn!tJOyND{m@wmT_BWtb#-$$-Vedmp_`3k)k4ju4;B7WG_&6S(t|PgV#vp+|qWFAuoP}o3yXuZY|DBNAq}AcV5f5L+ST@ z4Gn6DZ%dMM0l!3kk(2X1asz=G&P)z4PwbgzUS^m%8$-i~AZuKE*`0E|Hre^AZfZRM zoYNPGe{k@^g!J^6n+eVwd@am9m0B!rUu8TIjQaLyr2+ThV~kUv+0|{*pMBIHLUpf{ z3~KIiapL3uo>b97a1VP{62QAQ=ys52q@kf9ywmw$Q7G{}=pncUN>z6Dno_l!o16W; zJz&!sa~3`{Jw2_h^ZIqLKkL`f_}qsziTkzU!mLisOD$=OOW;Ko-FeIKN199pE{5eu zc&Giz3!Gx^dj3~O?-o}^pUWtGFW*A6+q0z8p&6mRpgh+3HgQLXcQb>H8h_BA<`yqW zp~m6hkz`O4MHL6_+CU5mc-wehq)CY*0lw>2dc~Hl?$a|tDuQRXkpBi{KLe`B% zLG`_beV?>wJ=1!rL?87 z?foTucBLxucPyXE;!?uY*ZRR9?LSEGB>AW`_33O;?J1N;OaH*$5D;z)6^($v()}?^ zipA~wYPX%brTMcqDIG#cpZ z(h}pYGTSNGuQ+j$xA#a5lL}d@`LX}wV~iUtK5hlVc`7y2VEwKXVK5@B?fSW%Bj{Xe zZ@B#Xl)uTJ%U!|OB5?~Jpl}`-3Lp85_($UQhXgg;2pD+4&8>$20if@JyxmyXfg~p< zcZ6a12q5hh;OI)S3$rl>6M;!sYinwxpH3tA%=Y(3ju@s{!}w?xE_(5Bf2!r{DUhhU z+2Rs#F3t#ylIg8_F}t&>jp7biiMO6Cj2zwv7O%T~RdzCnTFf*g_(&Qz%C@TVS#VHi z^Rh`}wfIJhdq5q6-}u!)uFSyXWHsd?>-P5G+sJ`(deE9gTlIad$Le;^FzM5d{Np%< zUkh+id_5Aq{PeL3^VMs~ajs{vfiY zXF%t6uI~Y1#N<5cm$Qe~YJfq}>Oh(5h1~);hMVpsXde8mnUzy%uEhlnldn~3CaQMm z@7uaLsxi1d^w9V~fKpNfHZL&FH>Me~&^afyxJ_aFc5ey#FL6CDnSj zl!L?}Xjp1*^j&ZzuKZg731g4$jh51$kyI%N(Jp=5cB`YhWjfaPyfJAJcjV;U?-!iP zvgGVJ9QwES?A%_qg_}j1S!FsS|D+m{4dM(t)IPkt)&h^MfA7^3OCUDg0-))?UN=F+ zKVX`7m;5h)m5#Tr^S}g`?_dm<;xY@$3XB#5=^(GBrKP25to8}?ej(_l^5L3u@}!5% ziCP&k&>Q%aYyiz}(8E25e#?g4UJN$xKv<+>Aeb(sE6sSKA5x|6@xZUuuiHd6{#)l% zcCrmUC!I6QshuF8gKJ&Q)}o;yx1q6A{9DJBPF$5$dlH#)(kpI_a6j% z@c6d-^*E$_v+GN)&%?6R?4{MT(C8KVs^3OgcVW7<5oMxx*hPLk1YxpE4p^%Xs*VBkq5?wXa+KI!yi0upmqOx;C!Qk> zfc+C7jTqz%Kxt@hr{jfQxz3)8jG>q*-0C)`bC;SaBULQ@KE>pII&Jpc{NnETX2U)1 zmG)b^067iAfJj)LCS6-?q@JWk;z;QA8_)oN=6!0b9tGa9<;eSU<-zYow46!3)MyT3 zLlG7)2#0N?0+^EHxl8o3{UkwN!M4?SWt`9hQ)J@JSBepp7M*DBR3aYot-7v3 z?<4*X`^CTr2c|a^KUyg(E0eg}R?$8gdAJ<`Efi=N80DpO=p!)`BWvZvKZc}i4i+G^i^!TKE} zT$#BJMJ)|9=-ZBAySIQ?r+H2rgS;+6?GU(IXB@J&$n%{Hw;y4_Pft4Jh*OqK;|$ZC z?!pu!jm7t45La~38}(A|EL`rJXW+FFQe+WT{I zUXYWXmKHR_`{Ab>d(9SG;~Gj173JjBBgw&MYKWT`$t8YYF~+-!w@toKI2WWZ!$HB+ zi@*URnT*uw9YWs{)ZyU7bN$66ih*#lq23{Sy1Lh3h#xHRfjtBxFdCMVlT$S$E;~Eu zXjKN(FE=db@(Gogo<>$98FPO*m7i-Zc18u_@v)i2ilf5DFwuMC{*hB{UUoftCO90B|#m?(-p~n%g$!s# z$g53HKmVG}jRL^Q!m4328!DjkINWtV)tZ&voNrKR&rEI%6B77h!}7o0xw)%Dz!~7Y z2s%lJ>NhyQ5QEDW_SgaN{2>GZOpkuw*M!=^s#S5@+pl!W-Krj%{?eoJmd z^d#C9PBsx2LFJ+NuFG6ZU-32uBELVp0vNe57aEc zOP!mKuhnHu*2Sfo=?UsLy?RF(ldcUgv_smcOnMafa7i6>UxQ&dfXy}fALFz=hvSKt5d%S}`iw0@+1Im=#3g@6nd9J0Mwpa3rLO^d~l zQpd|k&c#eqQs=dLyq0^lmtu9S_8&Lzw|bsR{-98-pcNqNp$O_`{V$QKr15=LplJ{_ z83VAW($}z^N_z){0D4^%3mezp11l@r&aNLzEJg^8F6tC|`USrPfWvTkR_BueS&*>zl5elTYgv|Hi^ieBGt9~`f(DiypuM3Tr}`^O zu`;e|FR2r(}1X~x` zx8Q9HXL#kOqF`(BUTZ@PBxu9MT1m;Q0Bg}jiHi6w0Q@6~o?$RvQJe_4SU~8PSJ6nR z5&=9De-9HEStm?>O9FWekIUziq`6N(81b#o^n*5Gn#A3&A*0O4`fs4Bpxoin=QUNk!}ccRCzu&+Fk!2D?4Yxi-_COHJ>b^#AcS+Xshi4*g2SsIjbPDohxPOwH|y z$UXQszo+Og&b{hA1wc*=0OU`w%KZocHdNJAwCexc4FQc+cpuYWsTAxcRsTl3e_A%! z4@M)v9y{zG2@>o9!TxbS{;R+O%WIDRSAO;X{R_pUeNmbeNNZQ+?>_a1073@nbaHA6 zn3`r$_-5*~m^5*=E1kZeY^ur2Xe&lIIXb8TNy{n{ctIZj>zSNloI>2E1U#pCJ*G=I zy^Hhe>uL%{nb(g2>vSV3fyL@fLQ1J-FY0*T2-zh!1hZ^7zjn!0@1TrJj4musEG}xy z%rDI@K*j7E#A9rot+AUOw~8Eh9Z8Azw94*Y{K8)Avfj1n5eg2vLAilRuJlacstJ6b zLTwX1R2P&2j`8@*h$Kiion-e*t(jlLv`lYctRaq4LPAdlkQo5PE=*Ok?Z5REy~ctk zP>{&fSJ-;#V^M1#0h+}NO|(MAJ)@?2vKu-E$1eCv<_7pgX$ zxS8g8=X%M1%G=Cf;_UqVsIzN#%f{tZtLLm!vBCHp$dJ0{sqky;UYS9p`nzjs$jB2o zH6i#wo2BGp1_6l{_=xK>C@GM=)jhQ6-RBsQ&vECq{-7@N*6|Upr+( z@9sd|SKsz(Q9v`u5D-4#6xpauUKj-O1p24YpW-^n9FWE~s?T<29dLV@79ff>PI=bS zJzgRx?Xk7?W~%3(6PaKWgsKi8O@&B>gnQ;(4S)BE0mX!UF?Q1cUyDiAb_=6)iPojF zkSYYNrA^V^%Idt0s>AWdl&snPqo1D(Is8z3NAJGq-&7%_bbdGm0*-EUv+)J!Cp8=h z^rke{>A@G2|L6G7B7u9XI%U(6!#!htP{{Yel8EeYCS`|3?qHk!i^t%2@4#W#uhH_1 zJL4HBRsj5ZFT@`&lU3)i9!Ug*gX90&n#{I+E!a&+G@DgW0Y6a*9uHr;%pVy*#YTQ? zaGQCU05Xh49>?ya>jnDEPbe!`?@q|^^+*wbB?_#relu{1_)M@s|7v~@WTJB_i$ z&mV%TcNY*WFj!i@2UbRyxg7AsqrGIRelxQ};w!DsT3K|flR*%q`1^L^J}R7t9QP-8 zf?5mE;rNM3$h~hMZ=)coDJf`xl$QaCVinB~mS4X?Egw5L#C0Fs=R0^9nEs;CS*+sp z;2E+;R!s0Kvd$J6`9_TUpv=pgLT60)$X&y(S{u3mU0T!$yQjq+C{$rQ5$Qlw%B<61 zmwDN4mc{LG=-9)hYjbm~IRoZIJA%y%-qcSyIW$jD=ie-8Mh1b$4%=#lc*s?-nk)0) z%?6}0%x95?@WfVM*;!i>Ngb@tiNyYCm5UvXCOO%ZeHCBpB-wqZ^RcRmg`To z6qzg;^&T(XV0r1+xmJ1?k7~D}q0U8|0JhQE0D^AQ4#9eylyC{C0SDBwgXrIwZy@(} zi?%Xy1BY`L48Zb>W;^Yu1aQ6YDZCH_C|s(AJbxtz@|1NDV8p#s2icZ>al76%JILce zm94dQ5Pf$7$Gd6*$X$Rs!F=cR9BlOp8v&qZ(`zOpNdL*_H5E$Kl!>ui_1%Rdk^)QH zLuA9)xs5Ke&O?Ac5ee|~G6X(T-;!wVbLfj6u`5R4=@KgirmFuOom|6p(Ct^vw+L2G8kSsz#NcczD0Hvw4FldAVXNflO1; zHNv?7lrvai#D#J%q)3tHID1(dX?d&Jn5cV^^>QGi2%FX40~Np`@yen?QbDs>}y04eF(!*2dS=uODas+6m?-vA>voF9Pd(YC*OQX;%k*TJ{%>!6p~)2PG@Cr9Lmfq_LMw-g~1?%p(b z9SR^5p^l>5m!-`sq+>M)zpIrMxXxF`SxXTe8ZXy?OsunJ>MUR~_I=~etNm`m5$fp* zH~)a*DK`M7#PC`wZolstEXGA3gX*Hi>L1d`@|u8kz?{wPo*_lj)MTM82cH3TD>WvZEPPKgmgGY15-AlU_=y85Oh?8oQ2X_?}qz?Q8C2s*-|b1n-;p*I5)Dn=^_^rjtB$C;p-aL`5G31|A=rK)Nr?9R|g_^DBuBRkj$FYu*v z4IR%qRUZgK&um%|#ZbqaMa7r*ZilzOcxXomyM$}a4bSf%Mc7>{9YwsdmFWT{aZT-Z zZwBuGWd}9rbElVC6PBA^fE5#=vi0S1XVneo0VR_GpqcUA}i9$EH3nI&fBhI z<42Qqo|TU7{%$~zc1grZGe&V_^kHRYY9>pSB1*C0=T#;kAoV}a`mnoR{OsVXL2I&i zd!%fxzK7$3%#1$NK-s9*)TzT{*WU`xlwuV^dKzyR(p=YC~@+sgLY0tM{h;^&DxYqRBn8>6X z=lQd=$Z9ovwiF!-H(F!9$Q+~NAS1bUN7;S3D#g-B$ruB>2hw>uk#~?-5)1+e{>AI9}PVR zoPb;vr)c4*7523DewN3A`~rXM$iDA`z7HJckk>LegYbWFV82-xNu!IwmvnV>Fx4^w zR^4INPjxx?EbI2|-c(Yn_qr`4sA9;Zo!&Y^5bw2p>}DaTnSq|m3^fiXw>!9`fgb2K z`;#Tx<~g7sv8D9-R;E+vLoegd7Crllj1kyw_#v(#Jpy!=eUxbB`gzNy}&4OIJ7zH zZvhA^;C(hALsEMwPKLSLDem?iV^!h~MuUO;0i5bSmdik6>zjI}=sqEKXX&OD;OKCS z1LlgC8FjS73-K3;wsh>((CgLU}8*KJQl2)ihFGD*=oz)kz z03m{Jo~U6G7hyD3}Xg?Y;ch6+*z?2OU+I?=LVEROC79euuZ)XL+%6# z3qusK(N&OAfsTY44rJiur-luK^88D_LZ)F(F$9?3O`rv)xU-A{WwrI}g$y`|;YW=C zomX-o!4oeLrI47F?#9;?+Ro8uO8l-xttQE%(At?tTklphc+ej-q1 z|6&(j2B-wQ0jW_6@)zWMvK7G#NUAEWS-IJWicu%(36)coDD!) zi2SrO$cUcOD~8=ATiQ19)ZzyN-Q*uY zh107g0|kMk4)r){=QyBSObLgD&K~EQ>AYD6s_bwzy^Kwmsyfq$vyxxt8n?!*f4%ZR ztZ`^PAm6lFmhORk7vOeB7aywP;)3QEWzwjCj_kYDb`x_|c@d!N=Va$zJp=R(FC%VR z+MX|@HSNS10QGfQ^#i(TA*fwt%;%3H5IxoS?ffJLD0y9dm}<5_w1f#EnbNu`WL^^b zB^IF=_f8I=cA#e0=VB%Du2aj>Mr5KHwvQbR1uPrUw0N8bJYYEF)VD9tT+!AnQjVw~ zy6*cPF#ev!ZC`s^>hc-$iX)Qkc#mw4ECKrYahM3x%rfJIXd5r~9Y%khl(|fYq2pby z5oGEm{AKXP46B2oFhK*MT)Wj5bY{{ka;a}a4&n&rZdL|>TJ(kM38sF?#+du8k24Kg z6_#{OlbmfshoZo5VRE|fZaEwcwlo9Uk-{;kPEjybmxMCm@HF!fH-o^^B`khQCWM~& zIh1?=(5P$A_0gdk^GxXp-3>s-E`z~v?#2=7z@B0hV>q7}^(^+s$Gg?%4PTd@FYpWY ziAQvBAcrBV?&R+a@VqkSD%$qBPeuvXy-`Cc=q`3SB#1m4Y=A@D=d? z!ekSa!R&a-)SLh6KhZ6do@~T#bEq9?$%7xj(fNUH5I-6h!!O)g2i;Ye6HrFfq#;|g zn)KoE4SEsKH2XRgaI@6K_Uw{O05lz_kLu>X$furrRI{evWe}Dab*zVtQJ^8a6eE6F zKJ(6@;_%S<=8trT?O~bUP-fliBF*|ZLTw??wTJPR&b}qZ2eBswW#t0&08M!?HKpp5 zgHo_Fkp0H)EluhyNX*9SKjo%DU~h^o$KKpa7^X8cHFuN=BGcJh&vGFv*EKn**;7>$ zk5YM5CTS`DyVg2;CWRQ|!hKE@GMgtY2A4-yG&*_ZkVr+G5YXP#z_lxTDjt0%n%xmwL+aspprJof5g>!YcO&&q<80N_z9|oogA4ot{3J0tzC%Ci8m2 zAH?!rZwcYOjh*2zs^mpOLp-=&Pxpo_qyO!g0__6t#Lh9!3HU4x(V!-#4=m>~_mS5w zTX_oppfBM22Ovx%KQW34$hiZ`K0%xY`L#_!(D|nfqn~)^+|fG1^Jp%z5i|5s{OL_M zEjN8YOh)-V-;;uzfwlCWH|o+V+g-exO|VoL)1~N(czF>`ZEnHyE66x5PB4xx;GV|d zD%om5ylXqV?`Jq-Q?u57yL*NGW{*g9RQ&2)5&T@)*E+Tw-cgVa19dUK;IJ4;m2d$q z`Oz%bdOxTh&@IXm`nsP%E>6AZwj(HB0h}PpbLi+9h{9g~Qc!g^j-yQ_YMQpz=5a@m zheG?@_!KpIhlZ-vyJ;Fcph3|BK)oD$dG8YkH3f|_G<=>+i!}JOVPZeeLX$HCRewL* zA=8i~$p9}K9G0_jZ?W|j3@ZY8s^Srz39lyEF!Tep0yI*nYHnT3og}ZE5`Cd5y$nx< z4vwx@xX|!Nu-W>`C^oX@qZPNDbnXL*9Y|B%qmVzDP+Vz|u8qmaKU=1i1HTq)^xA4A&~PIxj#wp>969!UJe zD6>uMV^LtNr4s62rr5se(F!1F9&OA=CJEI=ge!uAKWLiat?A1vV;*5U=;Pic0vptd zB#8FwEMuc6{wwYrwr8ab6*+mNsq*$kijO-Y*1y*|ScG$cxNOD=4tG_=gxTT_zCPvI z|EImL3X3ud*PW4)Qd-I(2N=4gySqzD5NQ>pTj`bfrYUah16kyl*y3HU#OEh|e7oEMNp`H?Kv4A&I?yu1SOV%XB7E&K zG^h=j@ydp6ZZ~HSY>z2>F6G~U(KOIfT{|tg`c325uKupt#>ZM+{n{j%xmOg=I_rwa z0WnJ--(By>uTGvnnK3}nDh$+e&T^ugWCOpXU$-)}vS28nR*Pi9m+HK+)~2ps0t+Z@ zGd4twfPb;RK>8!z_Ls-0ShD+fy%&16UvM4NI(dPOvmA`xQT0 znP=kAa6577!O^vXnD4S*ISb`E+rR9Gd>QHuV4q(kQ`WWkI;3Kz!SIx9bM?m6WB!@B zD99hmldm@>EoYua0)G1mW^`5m*CYphd`vwn&?vZV%N$=H7Qv17%q;t8Vu&aH&NIco zc@~%^hc>?kekW@nbqM)h*z%gBhqQjnWcgdNtk;4tkv0GwOlko3r^w>uv9Nve#@?YP z`^fL0QJrQ028$j^NQt=|M=y&vd>T2O2|H^%&IyD{om+ z5&8lS3AX&XmHjIZz_F2d0X~C~OCLHKExQ73iDw^9vrPfGzrX~RWE<1;h_z+lfYvNw zh%(w7G|B|BFiLe;ybIgLI6zVb#h*v;JH{>uj@0+D4oT0QHtx}*Bm!RPKbr+q25!*E=5K$QX>fTChewyK88aO3{(`v8P&+*R&)#8qqx=u;p% z*dynKUE?>7B%@Ncmi5+rnqTC_%TjLe?*+ptV_wPf#efhea<(*dCdtcy9 zeqw}O`nP1aXBM=_AY_~Qv)|WG3d^J(c(VHw=x_s{rBhO`RFW%L+?^L8Sl$1d>igK6&t#FSz6DWTU{GtW!|0 zj`tXI?WV#n;0C(fQPC~8IT;c!T^>Dgs~pfz70ghkM*lpofO<_HJdcsrpbh;1Yc-_|7I3<8@CZMaz~{=UA4p8s zug}~!XiaL$G=n{AdON*et7Y5YqV*9tdV^i_+2lA7XbTP7%U`|L_9jepzZ@oAa0z$$ z%zv)770(x!#Oe8jt}RVm?ub;0uC~#oi24+pSCS4SXYc`7H15&;wA)Qzmh8$w4(X1k zdfNZT_0i|ry5QBO^MQ9$w{dYX%a48CqZbPjPABcGO$AGwjh(qN|Jc`l`aF3~fFW6A z4dt!x5d;4O@|*+ms$mbw!0|$2oAddh^U%S_=074@L;a4IXWUY5o(%nlN-cs64YdI9 z7Qz+jif_}56cWHq3Wq%(1qqRR#q43Dsf6JKh+)xs@X>tAg7HLo7d?!Ap0%4u2^~+A9igzRX)XzV;v+O)?aL5 z6hIt*3ZU7U9e=XRzPmuH3aCnTaL<8o*yw7nWSjW5Rfi665?*^EpK3qJ`AnDp>h7?( zra1^5LX9V42HZ5j5-Aa-nzFYF$T7Iw)>t{*82P|Go&uAi98v}yfR&4Am>iOE(fv;(utyl*+v9WYXtb=5>%7+TTNdz;fSh6@FM$Iwnze*a1$C;j9xYz- zOSawOc3??Hr%F%OPFjeHi^qIVNl4ptc9^lg128c9Ux2OXXTNF&tP(f*&Rc4t+;~Ys z@67aUYg`M4XA?TnebSSh<1=l$=IoHJ3jvbZeE zf|Q)anhr<_lwR*kIbk&3g2mt@L1*OpX*DE?QPjuD@%H_1#$@5MfPlJtm=5G*Z}g_t^X+uiCc3<*C&_kSt5IL>o54`^ zU0MftIY@=;%6^;)e0d_XPW3SwgOxwE&}uWaw<8%2cnZPvv{g(1LAI+<8h@IUO>#z! z>4^sfP^=_>$ySEgKP@;s1=8G`p8(X6s=BMb<@F$aP!`C}y!?YB0oH5T8JO^iF(Kgx zW$#}_g9h6h!Qk4JYqM7J?O%?k?KWAqcK&z)5iaoCTCHrK5cV~L^yX%Zcv$FD;8KPE zvKQ8goIkB&4VW zI=C9>mjvcWJ8d`iPXj64yn~v`FNNBZJ! zk_YIldsjqCh0omYPmk+@v22b9IIZ)@$^A*>F0`t@owyKqLCs@{@V3qNw}~7PV^t$f zq=51ZIsNExM%A;oA>Uy zd4vz^-N6E;7!1+e`_1_u4g;O8J62~hWL+cBBcMxKa}~X~pQ{PbAP=SotY((rBzjR~*-^-egrSA%nu0|NqhCm59>k^xs$A(GBG5V8rn11bIi6Z|k z8SZt8MQVesLIDeLhOfk!(*Tm}qLkwNKB5W>`4F$(Z?8G`gt*+F^*@6$eJh4}-Ax7G9R|=@ln{>Tx|iHnvx(**)(#%x zuK6|b%n&&sr0~S;x|JZ;OSsnl4f!OrsFlWA^6pIF^q^mIO=g5dYo3Mei(5@(?iGwr@@Rs}56-)c$v(MkHyROfkW3;4* zFFNg9q(X5h=BP%a!gywcVAYBeFp>0lKH(v9%*N%>8kgtXRFbcPTIH^aBI;ktA;WqF z9XOTTRjeEq$vSwiIXY7c7#G%9Sfi5P!~&JyzDwuU-2MpRT-D9lbKV@AW0ot7Tx}!O zZJc_Mta$0*6bT^?FZ5Cg;or6S2s+t;I@ZY8L+!!KSoHPv(a({j9Q)F;e0TCM;o+%c zO$%p?UuxoOA%F0oWh=h}lMD*sfJ3NM=7jx@cDVFH7q9m|aYLH1GCUwDe{yGJoyrY) zKl%IWH3vQbJEw|q=xHv?6Ka5Ah3l%t+?g61wMd^yET@g21 zV|oCY@Z2ihR6m5&WFC)^-o*Hd-enQ!G4t8Lu)e_2ocl>ELux)7Xig_@Lal=Fi{Q4e zO+fvRR>a>w=lZ+y>Bu?=%R(VJomIVRRkkJrU26~Wc#W-3%#$-p<~SIl(8{nz29wP0 z5_!QjH!rAF=!rT8^=^6Nv#6RZG8dd|OvsQln)tzfDS@Y+B}c^YymRtW-N9b66Iec# z4`_!>?YVd$o>PziZtreh;5pL+gO&e?DAyGDV1%Y3bfj$|a&$kjbBbqMhuCxN5US#Pa!S>*dUs8zQa zzxvxY!{ujRs6&ax|Axnm`HSfQx9f!Vi{5c>KdU+W%QTB)^L)Q}ZpzDzVK;-!z#PE# zxvbb%Z)MyGM^{wp23$|Pg}1HM$Tq9M4z(hw-ZA7YR1_axbrAM~z$zube+3(xT|Ta(qH`)`%_G?fuO0@n3vUdc7pe{gUFMUs3ca3dp@JZLl;7Z^G@ zT%4!zgag=gsuahpzNq0lrV=ZH23>yo&fQ2tHRn4ykI=i*g(xYR!Xs|;k}wH>cl{ND za+MmhX7^W{PfSyQM4}Q;oW}36;Er#j@mg%?3Jn zaSvh#e)*1T`|I3GlQ1zrl`1sRd(*+;Cin!Y)*+~J3R0G^?T5Qu&TdgOZsYD`cvwv; z-WN#G@&+yKap@Y9;M4j=?VjMQ^aJFb0tvwZj77*oMoy1V8y(zys_>dfJ0}s5T>*c_+8_BzG@gJ~e*wI9lQllZX^mLw z^alO~<6hbo^Uu}%*sN6ZLcElwuXAS|MhS4&L~&N^Y|-X(!Het6=m6L0e7Pg)%`3~7 zw)rB?aY63jL<(^9-MExCh?`P@o3_SecZQw-za@w7WuDL078ZA$oJyG0fNQt;&Jd%k z>x$2^;fMmB+$fYD)yIhUh>5OZ#v-r4SPU|2n*-XzciMY_=$#0uFzyn47aycnlRQ59vHPnAWhYH zn01Gc(?u9B5^COJ8;lYfqiR3Zz1X!C$U1KG2mGAenRHGUkxY4hv~Z%Pe;y~V@a<#b z5M`neD?eDBJF*uyYBd@>$a~b()W3k4uoJ=pPuV-Yxg&3Nv5&E<0-0zD%jLh15v9H$ zz*oFOeiNGCO{@vG4(7EEDHLYtb$qiXkkKeaJQhqTaq)p*h3TQl@wTs~|J@-H$o?gQ z|JUi-XcN%v*yM&z4>cIdK1v%*dIxJ(EuH$V%T9&rhz=NKY>B#!sIi*Xtxj876S6 z263y}PR2xNWuwI(9TnZ9``y%C(z-P526=4{XFxT}HZhYjRcj@2X<9dJwqq_T^u+t8 zt_`48O|f~ULC4A=v=I%rnEigr5^iXGw@;x}93H7aiG;>~jn{~fp`?_5N*`6uhzPCV zU({BMp6(iL&o(!CXNZZ@*o{sf@0L(HG+i$y=|~86;wx6$dq-IOAXf6?6Gs8;~b^B9~m1MboUS1DD7)m$HiCwO;_p;B%wMPyP+j$ay?*tc z$*92U%5OpPe$2SZ##Q>L0-1F{-DvB#QnWOqL~h3D@lWdNVQP2BKV$crcFn)6rMjQ^RQ|ZVM=Xw>ZwK{m7H`Uen7`b1OKj8ix%bm2na!}`tXUpVA?M5v`B51)b&QA z!y32u`{j9rPLn9BJ;r~Z6BHi{Iz8Y&bV^T{8cFW34IQOwkmmWMYF-X!;^pDi~X!eY~oYST$8bitd^EsoIy*PP53C)i~WIy5ZZ|=^P18hDugCK(qFm{ z?5w>W#(FM6XzRdz^`+#uSSYsX!k{&rXjM^P;Z;I~FsBpPPgj$Mcv@%2>mHiw%0rSv z3;2Xgh6bQkPaJdUkdT_+JD>^Vjw7TH!j4zn&LYt-Yi#$@6iDkbt5_v*M}pRU)gJp* z7EAK-B)B{TRkrUfMAvuzOxg_o8tS{vVQPVFmoAh^E)Z%MYuiH!}hm7e^Uo}Mb@?4P%71684CUU z#zq7cWWFTynW7u@Qt;l8zmPQHamHk{$(ahI?to+rF<>a9>Gd?TwT zen^nY8s5-g-?%Q@d8E6@RfXLN!51jc(Q(vxwWxF2!)5`aSmNMWy1jHMQ-oQFot=@1 zaE`c$*_{)sV>k2LqgYobc=FP=mR-(3#g^qFy#krWb-r*}beK~^d6=RR`+%>|Y%!6Qu|@IY=qV>jRt z;^>iV6@6hJt0`*IixdR$dvH(pRy2cXWZ~Lnd0SCfo7PKpA&oYy7Pk_x)g|84pV)VAJy?=K>f1mjf{Z(^ zGDYpnH|~MJ0w4L&c)(yco&FzpE+%6puvf9NsW?M@$u1X$&Yyt@$ZvvC)Ezj8|mtg8~e*!<$1!)lHVqZpWOr$;$N5D2xNwwkipe_ViC z=1fs?59_a|Ehrj{Lw3fdE1%u1>K%dl-oc6d7n3?3ukFXzsG^hyU^|8i+fC2Pl+>55 z-jtkhso7i0_RFN%I5`s>;j#6x$Jj45jH00fBlMIA&1ZWPOSnuqGN=`!YwB}?6gG(S z`wCzO_Q@$cMq$p3i7aLzDVYmV2WIlyDYHI=jvFl0DTzUb=`Ofnt!dpg6=|u&Z&>Vj z(VxM|45~-=`4+XEHd^7D161xr8QLH1iqP7|##YDB&Vkg7D)Y8nDv#k8`gBWZBW~XA zg$y6fmTF-go%G8liad;X0iexig>~z@159Qn6G_D^flYXIJR4*^!XuRplI)-JQ8yfu0nBlEt3ds}48SX7EDw@Xlw`Yix|`e5UmjlEm7@qN854B|yqPTWK>`xfmx# z@)}O`R5O^1m~>{-x#Me~v06_b0);!a)~#4d4CCmq5Hq@iQ>jLIO9*0bnQvy}9QF;0 zy%(h~9z{a%C=MEp355_i{t*0qmR64$yPZyE9+CCp51lo;d<7lp%uVi#@}MHH5tJ{i zTv%<(fXeiiIfS(nUG+%?zd?WQ^x+ zy>wB=HN$5QRpX4k4KqGhB<4K4u4-Z~T_kl<5 zGm*;zbl*vmn-;Yg2S#`1i93qqGLn~6ek^y_vyR&AYpc`b+*J;n40KmYd*RD#nig4N zvosPU85FI$+tV3!J(XuY zbs3QvZFhA*_Ma6EvRnO*JyJ?}$3KxSpm3MOEphAkUA>nQ%rV~&5RlV4`Y7(fZoPJj zjH=G-^+z(xd50B*j(dD6W8}Sk%C^X=Q|q!vn{cp01NY77)nw>o5k-hZ1fE6M&GZBq zFYjy>t?uOM@2AuZSA1&+ZrEUWBB4l7zHkxY)hPopkvBI{6$d4G4D z>5Yd2RoO3gkH2M8iH{THjO{-iQ~Ds({eAhrZG_r38=sz*zc=%aWg47Ztk6Sx@?&dj z7ULXWLeks^3+0!TwAk5gDn0`i)4@CuRp2R#5ci&H0DXdS<2#t%s{5?Hl1EOeo}QjP zTYE(hAF{pauQD&sTFKK$aM>B{7O=z?01z3z7dTdWG$fGm1?`(3q~LEZmM&r zoajG44BtcMh;UE-gP}cmB6tjf1W@fG@!#)d0S3Vsth2^cX{ky+US5^^qK-1i`h4)? zM4Rg9)n`1Vqto00so`&}Gsn$cbEvkGxCFKG7Qz4jScEt{Rb(RZT&82XE0xkHr7wdYQUS0sVc0`Z~)C%?7$Igo(k~p zRddQfv;)iV`X{s&BC~35Jx|E&rT3ahYjzx?xZtF&Por1&h{j4+Iq+CTJPPSkJPvS z0^cI?d~_n($@{UgFe`@!-L{7cEg$2uTosKHcOS`p%P4Ls_4^VDy`BA;&jB%QF3!Bu zeYj+vbHq){aC6q%IEOcttasDikNaWS>^%@=FlBT;vp?@W?>^=z&1d|X9vZ(aYOL5uJf)!xU7HOJrX^>`X=y00=3TR0 zhPPpq8z|yi;rjcK8eak_I(Hs3LB?DWMmiQ7Io^S=xe_-WPzaw2z6`p*JB0-B@ zWn8Vl0=5)U8bU`r)52g?Y?et6MxNiOw(L0y+1A8;DwKRCww*V_tv9 zz(b?QDsz{c4J=!1F9}Ss!p)VvDrSVIg&s~++kg&G1^&fKsrl?;y0Pdju@z9+4ZH!B z-TwG`c9y~$#!n7>7tqsth?k>DCM8BKMg-wkiSL+Uh~voLgK2fMA@(cdMgGF>%;={B zG}W-ZXne=(no3|&B{WRKHI;Neu8Ej5=!Zfk=xx!+Nl7?deahBPXVKX}e4_N3)ZSG5 zHI$fIs3wexUEC>SGfu+5R2<^@v&~E?yxk;=Y`5~Eq)DOKl_cyy%xV|jqHJH86J%o2 zvoh)=LLzNGeBgXP=;+)L#Edd_FlkvcZ7GaV=!!Ct4Bp&qY*C1oDRC0E)+U7}zx#C- zKwVg%J-jcT6I?z?i|TE??50>{GF&|wIv4Fgo5Mq7S(!0#XZR`TZmGP#rAT^0=;(fJ z3Z%yZ^xORg6o{T%BDmYjW@H^4v5_0s9Ftveg^W>oKh1|@ux2Y#<&n1r2<9?G+Yd z#Mq?uhH9}vAA~=Io8i+m0$NMxMFlWza^?cNO-zCG2rA=_pK~i;vI_<8@qcWLS2nUF@3i5TGT!>-KrwNzsrvKH;__7$5qSQ^hiEuw2qps zG2jM#``_1^C@^;wlz+I|QM7njOM|=v%msZ%Mo6kNXGfJEDKuwEA$g}Ao{rPRG+{8DUo z<$DPkiEXp5dINQ~DcY|N^ge}86WAOFRccyPIt+M!93G}4pMd!^cDpaV&kZR4$X2Ka zR&-jW$>dlbt$(>QgzmvG4;L)m>?`iU`$`H=aY-d-3V|1^GzD5?uR_L&!A*RufUpXX z(>5cW6b6sBnSzNV-T9Kp_hQT7gLH7Y%DE8!fK<`zyX~8s^?;>N7i-`&Ut*mC6_Hx= z7w75#6ojlq$ZFxa8IE|e4q;^cs?4kAG+F>`b&QIHj;bcXSA;uLN=r)@t_3q^-kIMv zzzymh(C8zeRYsB3Rewe}Qc*JKH%5$z_!46l+Z4&vVKmw4X{@bPzBNe2>qo^} z8HP5fKg-M(mGA7Ow^p4{@k>br#HMh#4qF&%DMr6=m&=60+yMot0ketk-g0xXMD$b5 zZXFL&-5F`#K z!het_>41L@5}BWu$FtIsFXsBv`jtj-CUq}3*~ge%!a0!9HEN%{LwN1-52h&PK1vET zJR>DzGle`-I({u}ZR706T}R2T$2tVreJtHa$veh1Er!{j3#HB1`gU3`m=7^!qv!mY zGfKMjQRCP4A!LpE@?UFR5-ugDbJ-B(lv244|O-Qis8A4R-?) zou_B9IoI;5a^*THJC+gmn_%G;?NejI!eh%bu5GDR+Nk2UUQ+%q7se;uyU?005?V{< zmxQ$pXM3nlChzao%qJx?s{F1PUjqXl8nqEztgki|mt z+^VpExJIdj89B{m-L9GSL1$<^xA!^81-DS;B6q*_RFr)KW#uxrl@02<^pFP#Z0ZTY z_5b7*%3fcW4UjvSbPgNSV@#}VGQ{oG9=tWrQ0TVg)@acLnwx*3n^6uvF0SQk?MJat z1s3HW<460Sf$D9oEQVJ-g(nxoAN($0lLrd2HLk_bpyr27i zpZEQ~wZ3irS%20u+r|bnTr<~s)PC&eew+}cq9lWbPJ#{sfw11lN~(cCNWhPe#!w#t z-(FG5ApqYH+|*>mLFFT4yTFsj)~^*`gFsbr7bikI*xe6b2>nil79bEj z?~UYZ4R51^WfX5xk9M}eqo(bk;|#Z>wuqVnmKJM_q3Uyd)Q~X=hU}EQa0=AiPhdZG z+(%SYRBe~*{30&vjXiCbV80X=z9!iWxX?FK+bU;0Xa z-+xT~=aCZ_jPND$&*KIO+2pSm_lSstqyD^BXlw$a``3jb|GDr#?s>?H|0Ko#xfHAy z>g;E!QyUxkeQ@!b-jCZD)Y(mc;WU4b`OFn|F^faY0qQW3)z$qfjD+VSN}&@KnHGca zC2hBl)Twm@Il}JYU9WU>zEn4P1ixTWmXyqhL(!t9-Zlf#S*!V^@um*n^ZNMsK%Tcj zvhvm7Rra%Nwh}FbK7u*M6W!P!g{CurE2&-Cpb5387#O4+Cc*Bn_Fgrk|Ss;jH_D>{sPmGAeoJH&~#z+Z{j42Zd`Fh@a?bvvdJ zed%_}oS-Lo0z`5<0t1GKoZA)Bx!e*ff4BmE)X`o5(Pq_Alh zih`32j+c0k`3VKDh^Ec$1!iZ)I{yx^#Z2ybeE9(F4?NNs#CO zk3gvJm5PLw%r#{bV1NaCH9|8+jm`)-5h#b_NI8zSOrH?>mYhS+GkVIUDI^2L7eytt z&nqQ1lyjg&XxFL^zULWJRM)n1-aT$Ej-Z)(Cj?uzC}!8j>RYuA=YvnVx}8#& zi(WUPlHE5c`6-R3S9AL9&jr(kB`o8wLT@@0$n~CSp<74OH&#rHv2Bb;po zYWSo1@D2^b`cVHw`p08KNA(HcZu!!C!)4174xt&li1!H4+F4)H4T7!>&$$iLpV(Ha zqfNKC!}}eV`kCUkA4t7yd~HHSybd+S^|mQ=2-tdJyD#^9!Gu72cJ$ju0 zKGw&5StUO8;&CZi(s9E`*$?@HEAsLK>!))JWUX1V#z6t>qdWp$lsTp_A^UxG0Gje6 zcx9LB)RDBwbh0m49|3Q}<(1(o>ZVdOMzSj|SW} zNF{eXy#D*kkU6h;ga`}Ukg>J3G4?J~1;BPo6xoSvEc_y@biYjim9$1~vqj{zR1rm} zY^D8}QWw^?B({;$oorD}zz@s1I$0yXdHf~c&0tq;n}Ul1)fA^>*+mSoMAp*%Ky90g z)kOa?2m2VIQ?OOb3AsCIcBtzOA`*CFqUSSP!?%${I@7Iz(7^T5?RONZ!csULk*IOh z!_$`hUau^*@lRs+$UtfBPE{*7?xOk_hWBw5q1a_VtQF%o6V|l{r$lg&2rX zWjQ#F&arDu-hnL$wvQxot&0O;LN5F23?FwPbz1H=HEs`?g4k$ZfX*gZDY%6-d3)K) z3~rE=9O(s>hF7C#QwARj4THg8h>8#t<+ietBhNfyBXi<=FFU`Lm+0f)B+N)r* z+wJLrv)N-9j*sN=s>>Fwr-x?7qr)mE3c3Ut_Z;^RV{ZE_V7Ri4k=E{)O6mK}P{{^;rR9RhyMPF>Eo0T}NPNXAT89?dCzlHQDZ z+dSL}EvpVvv(b!jAEzpmO;Kd3K?G})dn)R5L-_TZca}^X4k$GMJgmQnqnfZ-O)OK+ z;qLn=#4S>bz&sh)b}_F+VBl`zaY2f_`5L>a@Y%myIzgiWxHQh%5Zag6jTw712q7ms zJeX%E6LCf94|L~F3d-47omy+O$H7p=ZeGozHOv@uUUJ>~DR=~0>$HWl2XPxwjHBtr zAQp4a*S(B_WIe}Gdu^$n^s{mzaicjjv>7Hw(<~y9*V?Y*8gb`@L(XXXat9Y)`{^ZV zMq?-s`)f1RjJi-~`yg`32)4J@>B=0q1w+4xm8Q2+bQJ|@BoRlc%t$W%0 zlG7PW@=|UITW!h3bl>_ya(QEo&2EY0Z;&dJH^)0H;H95xlYCC)4DHWU^F!=e>kAH2 z7=aaiEs@z@+11aKw8czj!P$#``IXu80hC%ii@iPmH;zH_Jen}lw*=K6x2-iYrg3Q%052J<}wugNIy4_jW_)Ckuyd#?jQx?&BnWYRv$u2b4o=HDUl%*ro_d|cA1 z+#KMyxJ~`0AzhC)#k0v&@U&^p$$i$h!y~H}t79OPyK3d@IX8&8+3*_wwxGz>*cz_m z9UQuEW1c?MLrNc9SkzmJj42LN~Qn<4A8#Uvz8JhI~ZRCk{LzD*OstR*f#Mpwbbesks`F(x%f&_arE5B%LZOd&*HDQkOmi(Of0B#!ML5S1f!T-+!9*S2h*3d?>l8`kl%S_6{KPk(kk19(s?YJ*ROa^c@2{9Y>7(d1j0%R zT=x<4IFA;xLlzfT%hQWXa7y=Vy}N^j2YjaZLjUSGou>K=mR_X6E|~Ql>F-0^t6`?d z>GzItgZQ^f*@IlFCP7!n5Ii=pl#`P*wqYGD6gA&;F3^YsJXlzy!TT=@RXpdhA945!5Hl;% zJzG7|ihxEuy9nUfq6Ej*yhed;e{UAfS%g!z$-vLFW$S${t_Z-!+h$_<9T6P88dQAA2g@W>*&B`;;$qV0>J1|oSIzQF=ILBBWZzbpT$z%FpZ`M>*JnGx_J8m{bMC_4IObJwQ_ZG?>EvHA7H+%&!U3RQ-)#30X61}`u zEV7QS1I7yT21;$!?!4Q0Bek-0BKqJX0h4q)Gxr*Z?{%OO@yqY<#)!2#V9iiZ+fWbZ9 zg(88o-z?s@YnIgo?#ESzkbXRUqK?5sny->Gn8*Ie8#Q( zt$K)yYOqE2r;@ialXBTm32u_EhSB$(((Rmc@JWpDPRhwq5*?@@E#Wv7^cK!Q;#lMm zgw$Ur%3GAJ2@?Pb?UR>Ob1p2Dx;nJ9VL8qhLAyexGbSIhY7dQ`f(HQlNC4=!Y&H{> zk|ZAj;Z&x(eZ~b%pUub!(DX?=g8p2%?C^81!9@XJK(n2V#7I5Ww5BJ>CHeh;sPWBdFr$eRe{r)K(E}FDKc$|qBlIB zYOngY72^~>HpezX@g@F-+^zmu)fHAj?TuTP9zVDn0Yh^3;PHoul1aqFh_ED|X;J>l=5s7WV2jy!RQOtQlLfO=k3oi}z`gjzps6&h3oMGyUqzdhHnq2F zrb$uI5>O3=XL!vNabcmjs$4hQcwfH~fC}l14q|zXqt$f0t*CjBfx2PQWR)E$72nuQ z;+kI$mRSmxa&9HsZgas0TZRHX=DnE74Oowxw{vn0cATpE6HqSnRvw%16gZPt zI&Eg#`-kaBl$piI2`sT?34Rt9%pkMjRjIBIa>nTFGiRe>aYo-l;nnuv({!asr!2}S zJS-2C@tJhBpG5{1aQ9?<{t6^=yy46b-*^;{UXM|v)NBaJeCBYU^A?Zmj3(w79|Cc% zZdNy=FV8_kojs45L`mgW*?-KtwY%M>eK7CFYy6+e^7JE3#Hx?;DrOU@Y`y`)^gP1A zcM51agnubt>Vlg14k&(GbhZ3*1EFcJ1?Vo^tL$PYvwr_%3iy-(B`DAPLu_ZI7KaK- zx9!_LjfKS1InS^rPSuFj6hi{QljR>=tF?U^7)q*!WNJr$sf`jOKH(n}7j(ODQ~fNE zf#+Jmt@5ULE;2EsZPpq=XSY=7epEaqktlB{$U>I87QG^!J7$oNn4#kP;OsKzUm@z| z@>u?3Lz}fmxx;0B*GD1EJO?GQN@&*um7SeIdBBGPlRx-SwA5RYeP~i~-r5yq{q=xo zajjShWvpt4%(gifFas_tjo)XgNwI(dO|dG%c>y#|2lhf+IUe9AD31tcH<0HSx zL^8P*YAjws3x+K?pR6!rm8pR_m{v@({?tv2JEhsE$Dd4tW~esRFE1kV9cdVPnRqOd z=F1#ki}Ksy;t3!Dawb28e9PO1idn#hLigr&KJqf19{&TzgO24C6?0o6mHV;@Zr{W^@8289uvJ z_DxAjT0h|eZol(B6AAlfi@ea^u(r*A89II7_SDG6oqZFp z+?rBm`h5E2>1L58(D_GKsAm~PScTPnqLmIdD7COj=wL@zEcTSbDRrJJ6JZhRg7erJ zF}wkU!LR2VR^Jr)vnb@Z=Er8X0L`Z&=BC}XAyp@?@n0diJQ{m(xu>paz2uoM#gbs1S7Dl(TT-M!YjXkPpXMH}S_^K2ACtO7QK{^rTx zYeSc^jatdA1Wl#Ou9LNHa%&S|qTQTf`!)t>l-_WaZccU4f>FEUt({2+{)>iXPg1Me z2pJR$4)@jJ2#Ri+Bx7Gc2{6Fyp~4b7ZE-btd>3nFay^mxVDSCV z1$YP97=5Qi77?2$We{AmM9aflI#_q}t56q7JbksudA?$5?v@E5S#cGakDw5sC>?I8 z19Yl%4trD;tChC-z`rn}zjyKzk@Y@I)(M0e7ZcN6lGVBt%$Ssv)y^C$HQ&(>3KDdz zhcKY`+bd51_1P4rAHtU;JmNF<)@L^WKll_Ex{u}9a>#q2e`4W~8jCvA|H`NK<2P+d zz$>$|u$UazC+edfxAE8p=dqge#10NxP4yRJ$feQ^2O0?MIm`eZ@$G&U2RDtb#xf=@dY=lT!s0jTIEMWY{j~`R52xIJ$YHCJB zK*JMkb!tf`YGd%3$TUeG*dTKS^1G!(B4vTG;Dcc03fawb5F$8){J!HiXr^*df5z4( zsoGB!o>MsA(buyx|i2% zmTBE5T`Uyk>`aTj2NzGqgKg^JY=D zkg-&+YxB3eu^Z&pss7d(>R}1@eGsN8vI!{8FYJxkA}>EfoStsE2LK%9i{UrCxVMd!Ym}XWg$gK}>2mQ>NiYCREkqaW(cctH z3k+&;c0W(9R5`9Z=owiGp|mZQ1?c7cmG5GJTn-p*@l}?WoRSB))pP50{>S`mgQ31y z5fZjv`6&iMYsE`u$_-<>abg*ww=;t%Fl5-Z0&u1&lB0gHun!>zz(%aWKp=9h*RE7+ zMRb3KZ->)UH*_MGsrft_%Q#;NIA_WYUmc6xa7l5I0&Euf?zkd5==J(xj^)}acbHw1 zWAJ$|@r>Yk8RzXKNJ}q*K87F?+5BZI7Qlf`*jh{Lr=Nper4?;@_CDvM3|F)>XT-n? zW~%&s3f0$o>qMMf41b0;e?J_ar!DLFe@H(!ZF07|TaeCED>vV^N|1=+(NAz| z7d3fFh0TBZUz?i-teg3wMeeY&JUl7*(rHLKuAdE)$+Ey2>(cF0~0HkaC;A7s7E(UUf*&3^f&b^p^?xpWIOFjD=fg0r zNxq>1eC57xl9J`0ceE>a=D*ia%y+Er(y-9a zl*(YU_(EmhIkmUu0B46}$$=cDc1JmR z1P||%AW%_~n@m+iZUxv&LeOM~1e60SfvP@mb--Sy$oRJFCV`@)q~zYiH2YJ@9Lg%= zNYGv-yge~DSUmYXx^VnEf~(0vi-$s)c$lw88uW^Sf?PI! z=dn{%FqJ0stKv*2&anIa#3=dn@N3zJBBXXDxCIICjp`>02bD6^NmIy(XnZ!+x%uHz zD*fkn!++gQ5M}j8qMZ-fg^G(W*25ZA*&hy4d@A7p2?6K`Jbysa|NV1SK7=_n`r2n( zD@8auso^tnRgsYc-~Vz4@OD)qge(6HhYy-IWq5L4s7W$!gVZu@*zgBE(UFG2n;Ej( z6e(mSC7b*IgzX3DK(Yg~b1@%#i722-0hcKpk}iION_BuuW&KT<@gih~geoW;*QW*d zEHPe0m*ZxzJ1SCkSo#a1lEB z+6OHuA}ktFy!SQvEJC(_gLkxMgJ!Lktgi&>9g52^%DpnxDN^=hE>7|e+04Lw_2GVIEAn7hiAcQ9>a{= zX5dfC?_<8OB9>3Oz(R4a;vmO5*V^Sz?#iF!prCH~J+@D{`v#=t{a+3~rvo*F){#d| zO<2{{>h;!$B%BZc?wie*3s3B!k#y#fIjM{0BjD{zM#37JW#LUQ1<7+&4BYA^$MJYbDEZ!xT z%}8`iaBWFLO`UG~kc!&lWWgmdd+%a%$-hw6nn$nHM``nZY^(LN3WkF>_tt%VqKyj6 z?1UQ`yw8XAXIs7UxhLcCj07FVz?X0bSi6t5J#&T6QF)h%Xp5-T;^P(PV+C8;G-dER z>sWv2N-A$$o5mM4S}gZ;T!JD*JfcbFrSuxb|5z)k}0AjPSdy^-=qo zM^sK#Dkn+aZRLUXbt<*e4D$?$ZwcjicPr{qEf;$ijcJAHAKxjvY#GB=#D9f~ANil& zoZawe4?pMM@8AJK{PwRve?xRO67QWsOX+=fyxiv4b4@AQ!5_4zpX(u`d1qG=GEvn+3|ijJ=u_V5{{0HbpP-N45ZvZY_i1*0@F?lS#A4b zE%BK;y4uU`kd{K-xC7(Ug{Z=s`Lf@<61s@*N{XthUQDXQ8#q*NDO==!o49$mS$vmU z{23Fmk12E4!M=`Uh%{!>j)RxI3NrQ?ml3p0uT@v4gPDg#;8W-QbQfYJpGq*qu29w| zL6kRL%UzfK84)9;-TB~5%n>ZQD;FqkscCB1)@7hSuJhHYxa_TlDB_1?6T(_b%{0|e z+uXI^?`Mj0n)h;=Z%Ev9WARv7hF`9#mO9u~B*COW$sH*Cv%L9l3eWLB!|9%uR>Ix{ zqBJ1J&xMhnqG7E+0 z+tbSLI^K3FE=q5Ei}{q5TK%?rTD*qITAeP03Y1G@BZbogZfZ$#AV~m)MM5+#GD`?rt#A?{`!DCB~1QQbS^@``clTEd(ra(P}k5cGfFGK~nl_y(~ z%M#(aqGOJD3wGf%=wL{6V5TlN}1~qcN=t)>vfdrBDWF>s(L)gM_L4iYZ1N$^FDkKR zQovIdRc;F!ZB`yJ7tp7_qR_`9bfdbw>*JT)uL@dzUr6TtoJXJ%7je85&FgSZpce;W zPGdf7w*Y4$!u_y;e%@OC?k+pRTFm$PjUWIV0#=*pzd$rG} zOuc^3zfb@mKF~MeyWzGt2O@2}Byj|lVn{_EM^7TmMq-GFQ!J()lfRK*ax8R^u7(|S z1f3g^hpfeCYBXIB4FMb^$mF$(WeB4{rOn!;O#V^_{avnqO6ijl8oH&XCs7U0n*`V7 zy1reGDzrOYmhU25@K~8kb=SYq&KGNR8chqHVENA7W{%k@qE1AWct~g$`dB_)I1FHc zf2=OxL-4-Dgg?VM(mw+~>zUo`{PEHF>Uq~u+Hd0w`?sa`U`XDvb;qsCwgx=ESxX@f zx5ZDZ^HI3lTNM?8Th9Z&u;Qy>R%(@miGl_k)XP1trKvqwnK#i6Hp11?`?>Q(}Os1Hpjmt^iAzGKr#MwHt z-muvc&uqgz|Dg3!*fKX(Ak) zV(am(@<`tN2(g#EIjB75DZVl8Xn3D6-R1}ZL@RowVv#y?$&_-INpU ztNCO2e%J@qw&pTziFYbK28A)}ip2mWrTD|W<&MP=?>2}te?nWR)58s}nxm1I z7_J9>aQAih*{Kcj^HjRvN{-1ys3-Ce^gFFIcgqwGdm1e!{}M|q7W&n7<>;gFTmZ87 zn9&QJuwNKreZ#&MaBU~wf{5s*+NLTV_NwaYP7Oni1^_W>f;xWQb9I`50xjTONL@Ns zb5ROm69RFRlE0Ga8Y>yGmHmMpk$@Ou#?YYlewL?Y zPonwIrwal{Mt>V*L%yh-hpJTT+6>|^=!Z2EXImtw*rejG)l;e78JS^LDB})ZqDqm5 zO&qS{7muR7sX#}7t4B7f59qT58fiBz%&XCF{=$6+*Oc{~T_|$F$!=YYTr6DmUrc8idBNLDEQB?=kn>!FkV7*r4=p*GPZb^p(OxS1_RqHIW zvtJs$Lmj4gvqh!J7<77#E0Gnl#ThAjp)7kFJ!_JZ=U#tA00V&1j3}2b@s4{z+FE7; zUD>#*H#{m8^OG-VM&j0T&@nPS>HIL7cN)&26UI^Q~s+G?sRtS0-OU7Ovzq?M@0 zN(a96I?kj_B-iv@>z|vCqxj-}SxzZ!#PG9_QK{sin?E^P*uC(ObdWMvd$pz=BqVTK zg=ifVIjmeNUMh0!U}5LX>3T6@d%xj0-|o@MZdOxN%}LYxI7SM0nTxqoMJ=uX3X|FH zyC3dD7l!WU4)WDOYY^>LD{OGkegB-axtmWpo_wmX$!x=MX4j9r z?X52}ATsI~qShFlE0MYyszl7WaZe*6U{a{pp}0OJDpyIY%*g1rlxS6<;e{o4X;{D1+S#3)+@wMeL1(NFZ)F3Br$P+IN|-c zXRz>R`{LFU6VD}$u@>}{y))KNZQx3L;2VqNGV2vQ6!#=3>?E}(VInyb!urVhh>U8- zIBdr+{7aYk31!&W3hMlm-Z;*NhNx^21m$hyMMx06l6sYLs~t&{$et3_x)oJmA@1!L z;vY2yXm|}V^a)kSkQ6l222`pJqDSxsTnMH3y;^pj4#PZ?n zF*RT!t34Co8+aezo!V!_^phhomvkn;H4qZu*QzD+g;Z}3>$m(OesbF;576CYr%=1K zaq@Ymc=`D1kPei4Pkvq=-9uqDDfN9cu1=ZmmcP4iFNe`sMvENmt2XI1%6*qfl?la8 zU%63;dG9SUsONd3IsVwX(+oPxIP2+((O=Ky>h!ZhRv@jtyp1#TN2?_cj+j?laXtO5 z7QLivb|W=Y*8aS&cR;A8QQt$nu!*4u3-8^1T>RB%Z+zmf^%kMH_e4|3Y0gQBWJw)Nf>g&&}whn+bSYv1dak^~u_HDY7p~A1aRy}nu7tn64BOnP5GazMp zS_Y(%exx6;qURat((%}hN(u`$uiUEQxnxkKO9PaD{rWB3wF;mF;k)|^+*6`c4US~8 z+sZF{Lr_#?W0&eNI6l1%BLRd6BLpsgNA&7~G20>r_ul%{b_RKH7}!nX{I&hh5dh83 zewmbz7jJv{`3}R5IZ%-ADxqG>-u0E~YS+}k4x~OgvqF(alc(=`^ZKn~VBJf{(a4JhL(HeQAL94u(8llDUSFK-g zh8!rn92kj|y^&!h-YZ3N?3`-I@KkRP?pm!FYZpX#0j^Jlw>~3F7hI6)K>C!)l7;CU znKS0my1n6tFUfep7|Ce6Pr^1kqO1)n?&Ut@Pg{q@!|_M)Y36yiqb4P~n5ZBX>(BIt z*u}mx8$A*+6h=%F6}KrNs3?_ojcwI5h-K7?Y?cvrU^e=~iaH!Fen2BWcicPldKdM zj&BP!yrlOo6H;#CK%ghr6GnNX56CEuz#r^E{QurRb9l^@fXJ@kXr_EI1)d2;L}G6G z_42|6Bt8+|M;eu=tV!ZClBaSTX}arP+teVMH{ag%%#*5xO?idI2N9idy>$B#BM#9Q z2a9K4p`50ibfAXYJK&g4>d{)w#=~)`QKC`=QQ*?T8`0U16q+Wi;|R<^8FX|fX+>st<#9xxjqsEgwK55hQwdyRyaQaz${ZYc#3x9_ zuZG3P8^nigIYIrR3>nVJo9sm5aQzRB1GOq!w4%nPAxaKi*KNVA*Y$6Zvi&>w zT@K96z{w=ZB2qRysER(~>qBNBHZViJ%YQinvr}oRMLr8|c*pJJmhCNXQ36O-7@UZ` zF@7aV(UY|rsJod_U3Dhm-Xd;G8|!(!Uyw=E!xI~N&l-@Jd*fD_ID{_(PO0rMQ=U&I zI}+p^6XItO9+OPRvt+1X-D_ttu0@jeP7cX7K{rAE zZGcGDs!TwbbYAWKoAC95x0LY555K342nyaZ65(eMNO=E5&i?&@IM|#57gMx8j{)Jz z?exa(#n~@Tv*OVP5e#9ona8J@k7S$hd+y^&!>$T?afE3==ED|>_rG}-Zc*{qdAR4J;(fB5k-XbzTPR^uIN^nh;khY2IQujG z7^+^8+Ap;;@sCRk>g9iRCdq`WEXvrCCWL>c3_Bt1`wOR z0*g=6`_#Ql6rSI&(2O{cq9}1<+l|}dToi)8MZ284>YqMdwGr1a+NrS>kGQBhcbd_vEr!v?OvK-0zU9F3X(0_?wbj*|2(13;~3gO*Wor3!B7)rdnfYEnXNHd1KyK97d27euPRk5Og{ zER46fZFF6aS#RK6yZ)_!_YR)vE!SG7XY=c+870A^Alj#8TeM@6l0lUJsIao;AV&ov zWvBcg>I@#KdJS%(Xd){5wii8E1ZQ?LPfv3!Z4~gFG?PTtY-B4Ht<_z|2#qrd*E8@- zY(A!GGsj^Rq2?&ZG<=X&s-KE#aPNz6w0;|P8F0!rdBJJ+nZqD&Dy?0Q1rXwAx>gn!DwrRdx4RMHcyeUcY1L@I2&X|(AdTA^}P_vP$U?M5cv@rGu*_FK?P=RTr!+|rOMqOF?2;Ht0R zd;Nl`Fae6&MPdK__xh;u6rlS5TfTE6n{+YNHbna<-#xB4jO~y1jJeu1=n<9=HA(7F zl(XqSXRUX_yuXGntiO6;kB=$m48QcwHJNOcdTj8}9Bz-h3g(NTIB~Cy zj#7_|qsH#aArTt3f$!LSSl(IzRL!6N6&U*m)hs9T|5nWZZ({!r6G(rd^kaT9AfZUB z*W^tfA(o3+=quF}p%2-19Ya)R|A=sVB7aR943SnLOAG4u-}yeYI-7Ys{*fzOyUheq z0Ix`vlDgbaEfhjp+i&g29|!I5mGT7vppNMpL}Sq}Z<6m*FtgOPPi*5H|Bu zeMKtIfYr|NX@_J%ey(U&11JtZK$KpnxwVTSus^P3cedGQXXzOcrIkz%&LohN$1ic2b zw<MVi+%*FMby}i1vO+>87bvg3PUQBqu zj_EFSt$+wqMEJUWS2F1=4NW_=b+BdFO!yp2Z#2@PG|gK9gwF0zwC%K(26cIp1zF*HDOXoF1j? z4BQo9JEiXEXP7509?CuRb;-7WNSb+dXw189VHf9NKWjkyzdd{d<{zYoxNCX7z5ZmG zJy1d_WB5Thf!tMi0}v3XBQtc!O<9}>WSI-r zzf!facsXh!(69A=EvgiLApSXsDr3rvGOdg2hcdkFr%r6sYvF_gyZ>Q8f7+%Sr{gMjq#Ub*`asb(?t zDQM|ozAnE2SJs%U02uF2g`q&(~mJN7;%U}T6^?=*c^TP1r=>YppZMXV^eA8l+XO`L-HTH;t+L_doihPuBY}|M zy%sJ;o$v8RXvO{xlvY`TRzjRwDwU}3 zFd%SG3!+xcr|cXP5Kp+E2;Rp{lxCltR8c!oe?``z{z^ikqXiMsY4gnR0<$Zft6!gf zULG0&;=U!jAFJQ))e=1pD~ad%*yni2l3q!k2*jN*`3@N1a?a;DpiL;&peK~@#a%Ok z=(&L5C7uudUpRGW9gLHwNLuUFH7Vn27U+ZQtv%7x0`P|GD5!OC+a_0M7eE>T$*^YM z`~VRUxzZCKoz(wq7t2I&v_YH8d}q-D)af>FPoMFKJT`*@7TZWHBl{3%{g=hT>W_X_ zNM^R*9T#4%jgX7QLO2tny~5aJ%fZ}z5!MfIAp8eno1&qkAmafk?)1~Kdtl~uH$P+c zrb#cXHflg{e+^bcDMWlfX&ub&p!`MD<`~c=zB<#2aphP5Fp!RTvey8tmZ$eZDE~$= zNaYT*@*LKt^Bb7(4U@W}GV1wj2mzy>4w0Y4bwEt_f=v{I(n5q3y z*1=wimlB1^*3SP8Sb=F?%tSE`3#$0dzb)A*Mq?SBUe;Z`ee>nw zBm@{#x15+8IQmQZ#GVd7D(B zu{!=rzz>}{ls6Ji#?#W%p4_bAp?FO)bh`XCa!WQvx`pK5htoFLomw$G?AfkobMin% z0ZMAOafio*pW+KwTm6!{=S6ly-p0GXH34jWNFsS5d{VpA#l`T}oF9jYj-et3J36qB;70Tt4#r&IjiSwJ^1Z3yp03-Z z*Rl#r(IWVUo2?4SUc1?}KBq|^^Q{z2>gtK|G|X*JQy_rGyr{=Q3=HAquvwzdes(`H zXWKOU>0LU(BB3YCX~sqP&Xz=x)i%2qh`@C3#X3#Y<-2mZMzwhFyyx`qR}ca4+2s7I zi~%LR2=U)rw<*Yz`v1-IUEE+I9COf2d3mGDwY-t>QsXmbw_nD+ptRrGFX8|VfZBsN zn5Wp@Mrn{GMu{y_k@16~WtEO^S>lhv+#juG5`KOytru#Pd$_?VZQiX6b@`?FY=U_V z$U@3TVw&0tcG<_*Q`e(p6kaurFFRTt`V((2y&XROIKfu0UNT?|AO9KdVoS?Y2O0he z92w9C?iH=X78vm3sO#9t|M%G;3!!`ikNajl3i#S|{Jnfx**Xzeg>oM^Si5b{@SEw9 zhv-fAB_HOsyL|*v%vM$gS=-^(Wkb;Q>XEJ2ao=E?@8!`OM9+@vK5WdO=E)wG%bD+# zj=sK|TAA^fK3vbKsYOgdGYkw&x9trx@YB4*%Yb!}RT_4}n7koDm(hrR_*v1hG`r}` z!~~sF+f;<$o=RIxdbXHAON4Img>`n(w*S$`0*F8T|5%BPK(GV-8v#SJ@KgmHP%(WhG2+PhXvgln)+d zBrll{wp6tNFEoHVPNqsL3kp2azHIgRI{IEo7^hKQ9nn1`UjT5B6PSYaP6Tm+TF4PA zYoZb6)F0Z9>^2Z!Gmur)xiMy^V?XGi)C>eod^l$|Apoq0I9Qhj2K<|lMNW`V330GU z-b0eR$0F#^Ae#`%!+Svoo|v5@Ovon6j1RX-2lDnmxA~9L{*!6{shj_=z-Br-d%F^O}q?1Rao4k{!b5u3o+U05iROIFy*{hS^EN3A)tHrU}=$p1UfEOxY0^ z;Sm@JWVx5T^0zQpO?XnVfOA|!A}Dot{c~{Vr`U_wJ3(mg*KBgV0atSXche^kIq;u) zqR#DANmQ=v^^*4{uJ+T*3PqPl6x_8ryl2De)G3*B5uq-de)D<)e$lhGu9xK1q0=yN zRKEB_I^z6xFs*V_yI%>;X*)8LsGFJ~b}RV%`We;#vP5>Cx4x)1Y84}B60NmjA;GFw z*At~<+S~8f=x^-o*lN4EcfzuR-ejxF8Y-6h@KUEkuI^E~H#@9_Ni#(2m0 ze)voFeeZqWYt4CG*PPc}1-hiPg$io;ihj%M$gG0QMAwt+nK`O?am2<><1s7Qc7oq? zGVsCv}$keapVu`+g&tUH*?;w3qM!X#;ksB$^+@wWLM#-GXOB z?c#Dv8DTbS@O!EKQ2oX0GdnSrFY!8kk@@uiqJlcT%yr^G&1_kX*~E zi+z@5pTjYJY@b{hA}aFgYV@Esn>=+U2j$~bLp zj|BDLi^JRAuU5V~CM8Z>A>2cO5p84W`Q|m%KHFbV{9h2_4@1r~gBQpy@B)nM)J4#X# z!i%-$fMvKgDA!RB!B7(c2XCwOJ*Ifez{Hwg-)zk9%`ba@3aBl6(K?2AvNnyUq~s9j zr=r|P8!i%Bet0C1h9Nq}`K=Z{tokgZ1^;%05KXMucrAb54*l`4JC41auJ_Usl|j1} zTYjxKMsbVOi-dn|A>lt^V=QOclx?1gPa#c@$OzLIVMpOz`u|f0-r0&Qt zxMlO}7FxHY8-ko{zEvv9Osd5Y#xvj8iA@n6H%GdI$H6minB^yN%}h->AU{7^erh*& z%=%&Pa_8A2dXi4-U*G$AmQQCFeje{e=7ZM&UD}KOH$>U%0ef@dv`Xg=rZ9eXR+&E? zsnwCHbkbSb_?TOkHnb1d^x6-rDm(;PAiSU@Xd!hi>zRSE z`nt|^sJo@fGF!c;M~STBZvWWKj9%@pFOTvEaju7hX%dQWY;{&29{jj}gwtq4Wm#YR zHzm&Hj#n$aWLr}Ae9VV51EAnuOn||5Fm!thE45udk>KhoMABIW?A; zOL$6@&;ETgT1`H-n(n;KyZ!osTg1@kjj7HA{Tle5=BB4*Tsrf_UCktAb#=GO0$sr+ zA4rLaJ$YAtM6d)a1oSgWDaU%2tz4Wip81CdO7j@4dYuHtuPxkRnm&V{93T;LRmqd3g2&ktwk6{r!7VSyfzqt5qE*TUn7@Rjzj@P!Yy!+=_HB8if2r&}iqqaY zAc@vw!?b_Q+N!V1FiLCNobJ*%InZVU%2odezFUWfUX#CSABlt2(A=F_H6!aMQC+Pq zYEqT*g8OR`nn94U$#3&0C7C6WcdT}kArU$wf|^O81?)sK7d<6yX!x`VG`cwM9nnSo z7!~@){GM*!Z@02$pTBODf`}LIzG;Tl>3QJ?3%6t<=_UWVJ1+fH zb;8>CX2)P&6WbaL+uH4=Gl{zqBzI7xJvYSPR@+XP#@&?=R*$~hUjKFA3Q|$?$o-;_ z)OVSi6ULZ>ip#~}t?!_1h*?*-Yh>mu@#xtP67LBNQ}^v3wTi)Ix#jvxR|qIg?_v#_ zJ}~{b-FZVNN03do>-%FVP1_j%&2DMNz}K|HH`WW%jHxFC9~ldOH_qV!ULDyF%xkTX zKe4AL*s$fJt;9hJ_oc@JtBA+7=G5~fT`cUS<8i8k^TO+yGg7^J%SAz?2Hdfvu?nWy zrm*sgWkc2X8fKZ+=WUPdhT7fn9BC?d%#5a3W=?ybPGuHH$B!Yg4(Z$)>6f05$@T_# zUA#y#ncSAU00~mG9z|#$x9@t*O3_NUFUCVBO1+-K_HJ8U!_UMB4$CUV^Jn)4Wu$L&gF@cx;jbkpCrh~=j(%%yvo-3}%H zG&0_d3dK`V1sOh`AnM{FmOWeU`ok{zyWjV_-qXo;3R%>=GsBmnIvw84F?SV@F}CY| z!=xL_Lz>z6WN?-n!aa;6Ak#iSA*rJ`yVOk|$MAi9f0_53XHX>2&qVp1%xGH(%8BRo zO)d&$5SAzQ-x>*HJD7$^XHUoG!5;u905<#<6|G)zmPtveR$z{LCF>Z~??;T}1^kl~v#2dQtvo%FiXB{^QKMYfw)p|Q2l`qEnoWYfM7hm(licO36C3BmO zV0Waqvg!n9)p!)N^?VUzySvLJCTN$q;0f)xQQQ`npKCFMbg`v=Je(?%F-Al zMvoU)H`;w)P5espJ5e(?DdZ^oMe0{&W|&lzgo-1b@JsHrD>sgtuPQ_(qHxHcb-W_= zXKX6XAdG=Cz?Q7AfrlG~xbJzNUKNOuo(TyFJ>A_VrlzY6AAQHi$K&JUO$*qR5VX_B zsi+>$-uuogU9*~OtHA`t`dM|ZX>!a|onFGv;QdaF2fc@}O0aBJvW)RF2ea$R_DD;Z z`1@Zb#wBYp9CWi$VH?*Q7meXcBKr;Uaf-_RPmAf3kr_J*(_mAW=vt>!6EMNoL9w&_ zGf6=bzLF#(FGFW+ej9^iALSt?GGi!e*K!&+C1)8Oa9no#n1i-VPnDU zfo}VOd6c&0ua@OBIWFJN=PKk$04HE|lx;Ol~bXNGUqxVD(4ilBN7 zhUuM+d|UV&SF$_KantYYUBlzxym;B8t$x4ndQ_I#ru2EnbFT09&1b0E$7&5nvHv5k zocqpo-6mhBaH{BF>(R6t5Qr895}MQ$7qql$#JpWie|a(xA*QBUz=wgeG4Y%P>tHf= ziF!%_iN)pk{a7Cec_GZWNwUyr{B~Uj5VJvda*Z6GH9_v-!4(~gQjD*(F>LjlH zVCwQ)@Jy+Uvg#%#TN&c9U~uY6O8N(Jaq$lyJ}4_IfBg7SP|#=0Ryz7)A-2OLy8BlPMu&pa%45(U5#pi+ zfi2Ncx*hxuS3)I4JrP7r7dm9qwV^|=i;Si}it73zCT)DA12 z0n8EQX&3ma33in(T_0yPreM5Uf#_}Up);?}wb40X5z3W!V z(@00b9QpS-$jeD_8;Mbk57nA?9gutsc&mB5cLF{l!SkM&KaHa`edU@m0C#bbw;w>+ zJnss84<4X^7B1X{F7y(qC(Vqr=$R`b9MnVPy_1~JlnqNIHt(yG$#iawl&tHy;p_@r z5gyCyKaACK;8mo~d{wRIytSU$H4H}zAQeTCjDbJcq3^;EW|+;4s)~C&%VIZC?5gKR z`;GP8$dg9i-Mimx@)dBEb#=44F#OQ==xA)Ac?!|6o!J^Z>*w$vGp??#@|22n>zrI% z#F_CU@o5!RTNs`-W~a!tUeotYC(_-D(iP=fuin{xq5n#`+Abd^9%3@$jS-cVh!0~uav>L9l zu}CW{*%vd}8wQ8(BF^*2QMBaX4O%$z#TjT552oo;jyiXBSbaKE&{kXxP?T@7L&+zI z;@xF7g7*DnZVZR1Y}nI%)LKC%^q$teir$xV(WXO%Y&@boBUxDpbv)cVAHG5#alL_E zl@#e(z1-Lg9@c-Tux}Cp$rYv1O>0AVk&4CeW0{IG&Qghqq1h*A&071QY1YP#dDmjP zEt^s(;)U#&wk)l-UyQ*Lv`KJt$zFS^_65-IN8%+PU=ns0Og*#7n(sUCFgR&u@sl+O z8Qh-?ezj0{3U%6rnQfkYh=s$qcI?_ax#m9?4M(4H0m&!vo5;!WI8lFd3)gKwZGQji zTL?wg_wV1y_O323$!d4!8$8bUW?jy9xNO%@Fo|8Ca;Thh-o7GG)a&WGY+6F&H`%1qZ=fV7l>XEcGd~w?}Nup-3Id^jUvu3b1iaTWS@>3veFYXG@B5BuV2Gnea z8HyWv@@o+XKV_oyOGBlBKN$O(+iuX)7GSe8hq2bCFI8E+DbFxRbt-8d$dy^V5?@Jo zD2yVNA5*{h6rOo`0rs%BZy){hy{c?LQ}Rv@Dj_EnX~U>}^y8kDTPwBif_J6p_Zh-m z1!i&DWFdD@;gj!^-KLKho(Ucw@oo+bpu;m&`$$VaCAikOSnRwPi{i&uB7NyGwy^=3_}keq~i-dzdA!Z&5k+A zt!#+I@NEQ!C!jo+R>TMYa}m4AD;={Hk}ja+xncrNtJNW8OPfKp3yDNP^i2O(`L!qiTY1Pg3j(!QoJ3G z#>bhg(`mV*t-%tNDt>skoaw9aRhJQFT(=yHYlbI{f>{U+b)Ro?(KcBdm8JY4>c(c8 zRgBBNKXf4?N;tFcPa+P2I?@T}wQzl(k9$Tc&1n$?x-(VF5lWrBgbYl@f7ARNPJ-{e zTh#kh53=WDL*$td9}n^}`k2(uclH~DDRnOA2EADaQJfcwJ65Wb)q$Ao4lv zO!^|@>FVm{=Cv^r+6qHNM4A!V@2)R4Q1sHKoZB7N9p>>5znYu4&!(R~o!l5TQ$K@y z%hGpnMt-SfJgJ?WK(a(gZ*ie~!5vI7;iO-onvdhRhI5Tx?6ZIUXMe0pM+qwr-Ba2> zy8bOKwiAlWH!rP=<@2BG$FK}S;E!lw;*H^QmQ;6DG&fDK2doUF-!;OXA_b76q2u7W zNY2E?eKY*9$4jDI>uRZLWW%obK|+LnsjeGMiTg>TziTi-0zJUtdJVxH@g63!=;N;( znkNsJFup@Vv?M|cgugEtdLo5k4s_|g)f`-g15b4P)~c$Kww=Ws>G5G69B?Pw>gdPK z@La~Zaw(#0r}aA-61Bqb)jINB-3QW8%ug2QWw zFjHSpLSjWdEx`YM?Al~%+fvMlKRtVj2MsmPN zx64;G5VdOspfcm6134^Z*wLIiFEgF&)E9ixZaX|kwG=6m;o57TG}4|KM9=UQ1sA_N zKnB-`W*k%knel)|%+bpofc6DzIHTf<-TvX{kBUos%Ru%;pKOsW|_--NKp|n*-Hxs3)gU*lU+S?!$6fIbD zErnsXFt|ExT@SHs(;rXu|5{67eiVs_6pC@b zIX==iZx4r0-&EZkrRu>`(>?mE3DBuv~j@-m2JaOLK1<}wbqF)?i=l*e%V3@9ObXzHr7>T2`VYgXeU za^A)F9#AvL+3w82m1_Pf`x;Q|_X(+!zKU`mqASCIpa6|#cn}JFk`l22eUX(deCo0Z z)Kj4E|GBTacLS6OcmET=bs2UJzIi*})LozvIAY;{O01OwpSFIO^L_X4FH9-K`T5E& z5M2@|dr(7}l^pcL`Fr-I^KAnjepc+;tmh?;*;%h2r}ziC<9HjHCY;L3xA|dlv2sv0 zuu$2J-Jzfe{k~oM9E0td@%+S5^!H!~X3T_@x@akQ%lU<=d`&)fNECeWrmgPAxCzU}rB&7rK!r z%NZRWmNlaId9>TO38#EjI8i$+LPvCH(|rE)N#kTaqU&o-2{rkluZt!`xwP zvJgI3;TsE*=p5J>XZ0hOxMjE{Q3VXg*u9S)Q1yj0Ekp_RWb_1uU;!77d=#vg>Iwu2 z=JQyu62sqkI4N#)7U_|XptLEh)$#fAJFj1LD3HnEMROK%C*3aP4+6RT7RrKG6Pr67 z1IUKjz>3GE+YP8(jCwp}>dt_}AF{p*|2RyXf{r12Zx@y8!dJtxR|6INiGS*rMmw)Y z5Ez(ojiH$b!U^&GGvcHswVp~YiwdyCa)Y!$ z&qL!@d$^ z9C+?#=E|8YD=7U0<}k_$(}bf`X+ynS4OO8;1)J{1{Qbp)aEp>+$>wO<4W#=Fi;+R< z_m*@40yph~+Pd=ceup2R!gkfnJZ;4`9_>+4tpjo9ygy^*hBt`iuK+cJqbQP4P4*O~ zTpJ}n;WOaI^39OEBO7AOq@kYpntwOE^O=rlpiuBwxxXL@QQPL-I-)jpE?Hsv%Ggx> z`I{C8C*L@|$Ra<+FrF2)Xt`<6I5rnsmU3OQq_YyoH=CFn%W#yaYaFe!!I7(CqaYJa z_QDR@YK9M#m)x~K4T4HdriH|(PUer?UcIST2l75zW;mJi_9+Wh#!a1FJ!k0@ejl-Z zfjupzV#yfNDD{*Z7HjD5^f=~IO=NGXAG?2v<`0U!FQfYi>V%#hh`p21i5kP6S$=in zX;1RX^M1k<`BgF8mQ0K-jMFS7R-;h^pHvJ#YfvP!<>Tc2ve-YG%|_wi!^%k|PNf9v zXn)jE7|Q?RRT0wY<-t+tTvfoYIGfERI|)y`lSON|5sud)8HDJb%&2DvfVkJcaZicn zceubXW*@dH<#+=#xzG5(Gnli$Qu_yr!*@-y1{JV2(Ih_G0>_+ukjv=tKG+zLXgMTj zehU+EHp*cTR+N~H3qa&{m*oX@uzyFAXYVn(&X&IqQ7E?EnAca9C*K)kJBtqtJaMu# zUIARIw&d+ml`+|@=A+PWYtBIK1F|Nc7V&i(C#)q_C^gvc^A*s9&|*aESg25Db+1L8 z%q`xM5X*&WVx4z&8}W=^_oRL<>O*c(9!)?dAN+tq%0YoZ+!1RK7<^^rbdqPg^0b(Z zj`R>8UMm?dY?XFbr>YR&bGCoe&=Vu0Sg^4@m>wfXHb?h0Y*=00_%oKaW~;G}|BD9#)p1-jj+Iu49?u2rghEauL(# z)t0PpkXL{05E6&U_=D{lY=#6r?I*)@uLbkw;u!Z>Gi-a5*Ol*;yTGPiawNh&IG$~b zjDuX?17cra^5zjOX}GuV8_71TR<+0P@xm$l7=X>Jiz#iHYAo$Pi!J$s1@Gq%!Gb{x z!`&~LCR<9CW`*XzedeZeqRj-mon%0+W2QTZWP~RrqUaAu9$r687fwmKSrtWMeuj*f zRf_W(`Xvzx$MLCpi2xa!3ZKHA9x2}6y`VJPzda!MLlhI->lOhXWRzBQsU0&I5jN-s z;sTt+r#bWM?i0eaQaG9x*r^Vl7yY4*SsRT-;CVJZM5}yumn*9jj%;fWLx}`;zxRt; zrTOyr%uppb=*57rhsTXL@}}f11D(+HM+YcbF@iqtuVO|w*IFJ*#h0%e=VVP#{KQ6pT^fpW zS=C~-=^MlM+($p?c=|amzOLG(mr>PGrR=S)gWBoF#~iwUFaLjaDS*;^G1hihpzb>j z`AR={C(P8< zX(Xju{=Iu_H6IdkqAVweg@a>1zV@femP$Ga1sPeeI67+s>q#RXqb3jZK>N#=FK%vi zJSVXsA{6aFds|ywRZvhMqtU8&<>Ka^(J%~o;rUYmoGc~#Z#|JIIOE<%5mK&N;Opya z*q=C6VLAVuRtXaeE8`^rDXB-n%Vz-{Vks~fjFOV_=og8Jxp`l2Z%A-(UUqh4va&XK zA~G_OP1==;zuKbt`1p*}K7D#dg7@*F(F@*)D#@Eicpj9{6`3I}pZ-I@qKmof353I! z0YW91`c_p{h40Oe!BSUOhw&DSa)=Z=K0d}!ECF1Wn~;$3f!3Ck3?%W^IPTAh$1>=D z`GOxR(i@N*hf*U z>&grZYIouLp2;gJrv5YvOioUYjm2A|r=>+kL22F3$;pXBrjI7UI3}-HmSOVBV_Y%# zuitwU!x$ItEfp1#;4@_8aAIyh{zz3*Q&S%wAL+!mUOXYS&Q~X!t*xy~OG|*kfw%s$ zeu;{ONtedkR7eQHpp`A|uKPPJ==k7bV>>xo1yqNl6K_ zzMC8G?>E0GF>N$7G@#In!{rVv%vN_M@4YJ9^|Sr?{q=P`3=FUN3iBDi`A9REz$R8( zTz+XOws^bp*Tw1S=?9T|QJFx5ot>SGL(l6;mvUv-m+s!))_wc{l?Y3W;-Qe|aQGz@ zx4JNBJ`lxp-g;LT7483EHAFAgaC0>JFM)v26T>ai41=9fqxvgT;OC&lCym3xonmou zm?>;*Y}hZ@85o`m9&oc5nwimwya*L(H9%N6ULT-%bv=`wnMs~WzDoYECY0%LdwoxW zAAcgn6p3#eX3EXTc)UrVg6D;cEW{xa-L@ZxZ;aa@h89%y4oREm*;0j_1Az`Vm;`R# zE|;B?1LNh#`!8E?Ggd&H8As=LL4o8uN7c`tJ&S2Hjzjpjx3;{!z0sYczI~&ip@~pB ztg$n}i^PFpiFbB&HPsI9Tm&iyLqaa%6B9!qAwr=N!5!cBOnoClWQ>iCp+Y&0Q z_V4bo66GsK0BI34ggSbAC2pw0i`_W)LE$Na3JZj$Yc7f}xNHzpS3Er%G14n+9=ilr2}MB-oymdIB6dhW81+5zNoeuBWSOfpfuR5K6@SY zh{>Rd?GvQkiW;K#8;CC<({(PKnA|apvl!E$f%mV$QWw(S)%8A~*Y$#_`HQV>2~YD` zl{$hFf=s3JDa|k=rUBTqy7a?z!z>1osm2Zt4j(=|dMz0A{OQy8xS=5-LL$OaB457O z7vPm(c`3@g<3T$5lHz`hJs5RPbNX)H63nv03>9GUh=c1W>R$Y ztFjSore8YvVWN@!s!B>qY0RKB(1ZYPQO;3~`(O6@vqsNGV;HRAPKE5JmN~464IFB9 z6PW#U*h(UBK7;x&xRlMmc}qN@NQ6B?H`u7(er~bfmU7H{_@lmRTn?zrWnj z(a^lJ-x>vxU}R*3`IBmZ_Ptq^eH4-Cfq`t=%^rd?lO52B}RqXYZjw=}-zE;}bD z9u7`Mdb(YG&!+O)w*HF zt#80;_qV=tE?Nl1Nla|4|Fpb<0yBKx-#gl4vEZ$wq{3n)b#%z!i=F?=rI;b>>+8v& z>~pd?($?07KMY`voSe^mqyN%VG%L?YDy1*x2xJG+p|3^7y2;){ndttD7%3hSCfNlGq7CJ4@fce}BQs z%8JE>3(qVL-lnZBt1N>zt#@gus{rP6Aa^?KCUC0$t)qTwH+X*8YAt<;y8@{$y^)=Tnu{)vBs|+A;x^ zony*$EKHcc?K1JG-?6jfP+yJa$U&n8{hxnq!ryI52CsjZT8oDbH*5;Th+k6w5NMb#<`i^gkiTj*g89 z3kd-rM?*mYA6T85`r!&{Wni!dwgID7eMoVrKDqHSEFD5OD@)7AH}{2w>^$`&esCil z{b#DbM;aSlNK8Ev{$fdSadB?$r%&Y|^aDf)@ID|SLRRJ$79woC(1Vw$)Z`X!Zf*hs z0R1BY^b174#mLA=0Ima?rlzLEI*;I=cu$$dNYRO7L0(4`=Cr51ghxU`(yDW|pRacV zKvslC1R_GiXS31?u-hQMkk(C7M8r!sIu-u^`pp<9P&6#+5UB)+#y~Q^$L264g7FvCO?RboQFM4KSy@a( zU1N{}01bNIDB{jQO&z%=+}dV!gNctH;Ps>(AXd@Bb_(4>fE4O7z&YW61`CDJiU!PGdO4UnHhIjg5n236Bo_fpTDctE;VL@?yg^kHXEM z{jV|k{M!k;SZ5wc=11q}{N@xI?DUeJFNukX-3}JslaeL_*qg-dxc3n5Yzm^PpA3t2 ziiVSba?>}YP0YW?udAnx;7J0~2WUqMhm4HO11xzHUJe3w+{;(5UIElO^6K?#&`s98 z{H`f`baXU5{S~q1nOXSXv)(Pjf;&iXe?x!&R}ymmRo+}Q}+eg5ob z1}h~aEtW2}!1?tL)7jzLc-Ctqv;>^7MM^!5Jiq=>cYO%~f5^>WOl{MK3pEK3`twO3 zoO0T)jU6OE%gRg3YsB#Ci-q-dWxAN>+bN9wXar7sjsv$$7Ms@KnQX8?JRIL1ptaOD zShnW)*H;ey<3@X=E{i$m(xGlv`zJLHy82QzhIo$-#4VvZ7rz+YlR{R{2yWWiX6AWa zXY%GzlQBb8Rr;fGakuPX zNTTu05cf_@>;wwbh2lJU%e2uyA<0#*XYzfG!JoPrT+ed}g5v8oyb9e5fkF{poVhW_ z`ue@Qiiw|x(vkn`+{;A?js3t<%Se@YD6I=qt}YmH|479^)t-*6R=s>HS-f8c%yO^wtePISo@>p8) zCt4I~&38Ko*$J)Rsgblv=MaK{2j1|X=2>7P-k8Ohc}$>>OqL^A44ApLeLl$M-YK*5TV5~AX8l&@t|H{*r-X4oQ6)Vy_QQz+|EQeLXXho!M9XiaA zd3&oZx0)M%Xo2ybrK!P9mGhM(o!eM6ze4D({cXyN|Gk%tuh7zG0{e`F*5jzp3Cr@xX*s#jojtXnOl8hF z2kn6~6k>S#C&bR_DqBhS-MFY!XFrBti8Y(!9UHMQIZ%CL`4mj^CIp73@2y&OZ?Eci z4msmvQS;SY`5#QF>~t@b8;k@FL>r#oIj{jE>;uAh_gH&|a&W(4oNb+xeZ+F-z)L5l zCfA_!o)*i4;#vnxsAA6=H<$0eN|n2j`nKtAW`!W{bM`izZ}~rXNf|)U&kcjS z2&!3}1xr@?-V^f0`46L2liG?t%?O#G9}QGTF4 z&0ueqU+cbo_&sP4f3Udb3PNHU5I4dabp;|hgCi73ZW#>xw;3Xuo|3V2s#h7 zCT`$}`E|yuqp!bZ?Iz!{AMcg37mX;#Tt66~9$tV3;sSoMu9I*S->3_HZjv*_Lw9S^ zKfC7r?fKtfjg?s7@awSOGLG5qMI$Y(H(z7$TC9Xjyun zDM`wvF{k}}TBE`;Rc9(~%r*n}_n-f<-fmr?98?j3JCT&CZHMQFCt%8Kl^(`;22Fc-hC1+y~`e&A{wkhWx6EYTX<~!%sa1>wtqcC&{2feFf7bjtH zXYH+Ab7~p(Z$q0CC-rC?5QaykO=`4So{*lPb3eedttv6=a_#@AtDfvAodq-L$$*>G zg@N=9G>WnDan%Avr))_zH8qg=0V8=(nZGB>s7|x!%AzQ^LLHO?G0CE_c+;eB!Hk^ERDwj-%VY(VswmqLedl6=RYP+Oj z6!JYwDdxDRO4$tv+!pIQ5IXmj6o=)5@sMBT-$wngf^!XLHO*&$pO7ivy|BxJ){oPl zl%vp1AyJcR-3zvu7P(X?;^G1d7?Ca$MOyc*SkiQ=rEeG+rAbV%NJ;r!FAg~8pvy~b z!QjxrxV|;Ob^o|u?^1|_ytQ4EfI^|h#yLED`-BfGo(dch7XVj@wtcs?>A_6rUd*)= zpk4M7ae_2awq&I#_we3$`X;ihoQP30>K^xyEf`9eYf9DYd#*H4`?zkUTJLze?{rtP zOssh+qRRt)U@9mp-V%>7De*3L>xBo7#}8);cRcR=V8kFp%gkF9B61w}WM7p%oT7GY zm$?ds(0j?%NsqjwD=OwfM(0M3TgfFG^wGm$mgRgkbHkL&q)kv1bKqV_TG-?qvv8i5 zjra1cSQ8q*yQr@Wj&qbAOTXtRbeWZ<_N!Q{WNlXvw6oTFx1($@t9X5DDyify^p?~7 zuGo2Rc3QVe9NtP!ZX||Y?QZ}6wt@4HmCx~=NMIP?^%QS-RnO|p9O(@S zn|wTEFLZ*N?QZYke*#YO%4dS98DH~Q`M>axvE9#Cxigye>gr>rnjtiuAq*88Ma_yC zBmLofSE|#cy*#aSh@QDiA*se!$p5yi$>rm1%BG)wxT55pkM)?`slOpdqViWSRLx^p z#Pq%$~gK(fw8 zaAh4qxl=Na^ynWI0m(0tT=3k3?(e^SsuuO!WE)LjUVHjtDq0HV@*-=M&mS^KX6f9rLx+_7~)r3TN}=QcTv4vNXTjs z(xe@M2xLFr^FOu?_qY2X8Ua@R8g&O3_jFJYjrU@~2W;k}mt&yXvtNI`=dxEt#l@vU zvhw}`WAMQJvnN%T>mj z+BZfuLKCz3#3_Swv-N@*a;&<+2j(hN*xng>X5{@;;u-n|#c)bv=YB+JJhn1F zq2vEKa%67&&q~D$y|JV_jhkPKc_~XL!6Z!i&fOQg;_YQc_zf;o7pJU8>lEo-txPp(6@9CpTn%jqGHM?B5z+7_CXOx;)M2@78MzDPH<|d z8a94l+8=J><>lVpg8Z4Dc0O9^QnReXxjY6mYq$|8*mn~iw-Z2Xl#`RoAPXxiEd@fB zW~CL})3d^wy?3CDXJBApZ_nOr3;qCzWvOLu5gZA9ppX{_34b2f*weG!&%NJTdqK&~ zT~k?E34;xpl#NeJ0F8Hae4HlB_b~XkL~t89DjPwlvNlj@+}GCD$b-bg8A92ANB|{` ziv!;`<73NDzyHWZ6YS>HBBiUs6`c49uACeliLJ;(@q`LBd#Ab79;F(pe~cNf z<6Rv4z9C7fiLyQ3$Kg&pRgk=!**cSCGbg1!6-{{L@B|l@{G}kz1k5)pJ&l?UZ9(lJ zF|RGHFJOWTtZ=_?otE6^)KoWR$FATU4WP1ZW#u~81~usrNwVuf_(Fnvty9tUg#ia~ zH8p)HxdYJbh`3J~6J&O1paD07qQ&1rsDCQ?U}{5tba2aFVkzpJe;#j{(_6wpKkH!=i$Ce_=ycU?mg!F;s>3(1 zikzf7Jm#)mQco?31b(BBRl3xY9SAYQrQ_a}nV!2K#bhVB`9p2JxE$jg+=nndq;BR_ zRNlQddNwUrvZFH+-ja}3m(T9&ouN>ynqQ``3{}$%G4APDYIZpaXHr7a7HCf7FrRL? z+iq~bIqc|+2?zk{(`}E|oz2;f$}2S|a{mX3?8koHIT+~-QfZdQRaSy!z0*RY*Sza7 zs?824#)7;PDjbYU=G`ra{6S2t6R2`j5^)jVzIiW}7!HWswAWho&{v?~GcEX_@jU@u zYF4wLiVLn%Wza`J*+zn)u@qt5z;M6fF-rI150Slkv1;G@n%tpur}J?O*9vy3 z)Vd59wUxiln4AKZyPO9!6Y5u$Q@*(+)jFHo)gX$Dk#g8?OfQi=dth;Tl29$Zldt(O zj-_e|rMQwd&ap^m72S3+#X(%$R7bsKsz_}Fz5vw^0E6^!GP}eK+bM<*8~_^@@Z`;^ zPs9^WDMpWegTgk4tJfywb_*m6evoC-lLpSmbFBAQyJ4XRGQ*X-?U1T24X8@uj8}KJ z2hRsldExyroWA-fo|{}&UQ0PO;cAZ;GZIfYyHb;4F_C>m!K#MoHl46Nqt;vSqe;?h zCZW(Q_x6r`#tq>F$CHeXZYPX;Pk;~Q*|V#yydsOb({X^W2tq}i>>RthyV3bXLkUi2 z=I_op8!r25_UpM#$MZolofsVjr%EEh&b}*Y!uWse?C!cvXgJP#+}{RtfT)4=oao|Y zL>jmXPliNU4SJOu+<7xQvVpp$>2Z6S?0WPRoRw>7YkRymTXVmvb+@`2tJOa+5R0gL z=5gWg0bTGyLJw)USVI1y$KwpNJzd=;@5jhVAg>SbU84m1p3i%bK7GOUAWyf17L`5l z&3`5Gnv^v(fsWKO{|dE|=5(bLuEkntN6w~)%w?qNp{W=y?`IF)$T8(06m<`ycbH{R z3#T)nkgZe*^O&lAqe)8nc_ZpnSmmfBc|}}3x&b#q`a{Fxi{kQBk?P2!X|?#lryTC* zEUF4Mi@SgbZ6uzJua*Bs83`L$m4@%HN?W`kkkF&<$)3G=e|o~t3ORouJ3qxAF?F8> zs+%=~sA8HnB>Tf(5;Gx1r!8ts&QiWiu=w%9bRTK?nn=n|T*1g)(_TOMuuG+7CBY2S zcfBb{tq`8$alaAT$RP8IzM3-Kl_GR!3?CR*Mfzj>iQ1Q%RNnl+7e36keAS>JS^1vG z#-WVM$Z>aCiB|NUA5e}0KTJ~%``aNE1-Uy<^;bVV_9kEXkAc&!%s42p$fH3;^OoEJUdEc0?x3ACbV$mN7l>nQBgaHln>qFq!b8NTH08qtDyjj18mmN<0g=9kv(mPvRnOx+>J zdpMSbBl4!~G0M5f>C(qRq zG9GT;V+e6@dU%C-j}C5JFeFVU>b*mz=S7tMazDSpi6o16$M9|YM1*Zsl$o;v;z2iFY&FY2oo{q{X z6{<)2fnFfJIZ&bLXl)e}gup?&zEN_{+R1^x>fpZ{;)8?}a_)KD7TvRh(#kb*0jOkZ zK^J}1EUH#P(@fm&(buf3wr4w20CmIbGQ}`TPygdou_Vub%$zzO{ZnI#%P;R?6rYhf zsH!w(?<$nBft)$udmihC&ewmrhR*Q<Z+i0@okem?10O6yt5G zjlt4aS|WeU`<}C!6MMC3vO;1F$97-%&y~5`%H)1c~!Z>QXDF^Pb29GD^Jbo%`-MnMGf`TIOsg`xuyyG(D(CVNtGHbb;nxg z6{RoJpy%~~!t4@&0qNclSKSnIODo5Y_UvpvzAX_|WE!5ny5-0b+YI z!hbvB2~NmFCHU(1k!jz4LgO5SXcnNZoC5tFOuMPc8njlw5SX1ijG;NS=tZT9J)wgP z)Rlm&_ikk!1RF`2+qDwsl+3f-+<_|6|QS&jm#vbKvS4y*dmw0fM z^_$(VS2V3ng&~@ULd&ZU1k#+6I@3IvyerNj#H{a$iKT_n$G85(zFa%nrdMNUYggsD zwa)u(KqiuxtQ#ubylqFG=MW9b3Zq|Dk#tXp^lc?>PM;5it1R!8d&Klo`R=;m{@S#t zE+*oo=Ce6Dp!UEcJ%0)D0Eq>Z0fx~xh&(~DF;a#bVPR`)3m}%;RYs2=OgbMbfREsQ zEx|HcVrG2Y&wvNSIFQ0D`R*>3bL6t5U`>p+>-`$#W>cFZS>J;*TpsK<|B^{|l0I}; z5B&n?DYlA7O5y-+clrhreBP}JWP&-ynNPB9SeN1-@MWz#VL-AWw?h8z;U*VM3AD(Iw+}lP39}<3De8nX?+z#|-vb&8XFEtDuFsGuio#)j2jLE_Ce{kTD~O{Z zti^>2LHMkfS|4y<{c%6{bbUxg4`gR`)xHIlv-WzgW^;3M82#YKyzC4a?fY_VYQH0Y1q6)kMSH)pk+jj|{Tm7uu!k zUPvUJeK=ZppbzNZ**ObL(9?c)^>c|>OS)(f-BWUg&$REkrKPFy$9nk+y>y++v?c1a zHlO%M<;&mAX+N>JU;l~tDX0kCBfGsk8J^BP+CB@r=Yk|(6KI&^rL>mrVj+UrJ%F)ogR%ns;{|h7 z_~KpG#`i8E^C*H>ZRN%{SH>l~&TBEP!ILc@xemtTjm&*9;Wz#+v!Kw^HTLWF1}w=U90H;g&;aA(3qzv}m)N=rGwr6S*z>Ps=sG z_*g951z=gF@;M+u zDD@!Rc4Drc>S7inX>zM`yBT~}6!(l%jT&F3K0a=}-*&79Ts7DYG#fl-5OF1~LrTgQ)>=bCv*{*ij22A~Jd@RtrcY%ZtIdPj>4Z_x?~xN01Q% z)ob%@f3xt#jA7qF`N_#is>eyM*zF1yQNjA>&`w-zY};YLegJdbmy(4O9QEa4&%aLO zDuC4R*z4OnoBInJUC;AaPdKTuhg9f;T?g=LRxGon<+a3iC?Wd^t{Ipi?EjxjeeQ;j zhCij9 zekuu^UiF*O8OHmSgRXv361ftNV$UoY)Yj;NQb>>Q_bGK1X9(Vu zM`uaq-X2SzGHnzG=-A*>BKpslsgPabFNyFX2 zAl_nTBfo+6_kA=F4gnp`lIvEAJn$o?X`TT-Obgrv<>huOZT`;lU%N9SMMGjh5>nUW zW+#We;RpeN!?*54qyF`oR=xCC2oOKQ65z13zPGA5uf#&j{{=nJOM zL?Rx5Tv1oUbmvec%X7d=S>0E%iVSF|hkcw&bbCToUBr-A^rA_iufSgo8ot?yMpGMWPbOrcA8#ViY>gF z63zcgcNxDv6~mN9j*DALq|m!L_B=NDvj6OeK>LQ{m2LN?V}28Q3s7wsc7>?}U51lW zwQw#ywlt0kP``xF$Dj~)5(68qI$}%UcMeBP0?z5JOB5qA<$WjvBc$dXtSIGQUWmRwp%tfOw7!hrFyN= z@}i7`ce55Hg9%*pY;1BA2hSfeQ2!^UbtZ5N4@xwcRl4+~Pmxjgh1p@Nbc$K&|6S{F zo|E;inQ@HsiW2P!OdCV8n{RC1Be)a(vE>ELeW;O%1>NX&SlwDmZ#moh=rLqaoXPe| z^S9=MiAgbst0iup;qzDt;8Z0oievK>dre(UjuX+!(H&wO;z95urPT%1!pe2U47Fg% zfJfl0Dt2iDEOGnKR^aUo<_Nc7yI;OKeSInp_%0mh=QH$rP9J@0sAsPyKYh9838lPs z-73XKi8_p=(MDNDJ4uk=GPfYyB}pO~U_HSobn9V4#n{}u$u?oh`QEiT)kk8eZtQ3r z4&QH6^-Px4>KAS^1Barbj74lZK{WBlbJn=Dcwcp$9whWFx#iL>WVrw}^)TZ^U ztCE22+&2rzOYc3#+qa{Nn55JNL*f6YC*y;v_QB`Ag5wingd-)OMqEL8P4ULpQKJjZ6Ef? zd<;F-hns$mQz`LPp^3l4ea>RD zki%d^b%9tSVl{7BvNnd7W_UdBwC=w#{lA$fOP}kHwXf~DA5NGX$Z-O7M@84tOr^u` zFQc#rd53bdi{3&Td@Ciu_u-y!6z$S+wbfSbphxj$B1BAm}VXjohmy3nl zCubFJzE+5a5(|K~kvEKm+)MJJ$(JKtWzG~GePqtl-|{XGPVTp5PkqIv2JUt+pRnY_ zxuiCazwV~hIb+iv8G6_#)yG`&lT=Dq)v>MSqEvU)>p~%QAO$HfxX|9y!kyz zL+rEACJHA{Qfk`gS#N?h>q{hj*(!Tj@~8%27_)ny5MXx& zvi5fcCbMbz*^516sb#skU%W7#YItywP|??8AXz;N0Ar{~jF zc-9MC*B6LucXlqjzG35{X}(~fXFR!4`uSk{#8mz!U(AVH!DFSsWFf1vJGSeJjX>EFH3i|b^G@;@F{>+_3JS8C9i~-B)Sr_|22KKhfAqu z%Q`eogP<6$I~N_c&Mf-E^V`=@2ahSN@v96+tWu zE-caZc4rt1Q)h?kRgGZn;4G8;!!MrhdU&%xx(9FF-6RJ#e+%G6igB&XaDBL`nmk>L z5EZx_NHUFfp5l(_<-*3n8LfJ#EW+7|uUa5ceg0Q$caK~@v?hQ>&EfF-ad)Z|QL5eQ zHIHi3JWjQd`^>6hux9^9*AsN`MGjUES6||0a&b||q;EJksJBRNC^u-q#c^u8Ju+PA z<|V>W?Yg!r4`V{?SM}JZjwauQ5y8@HKcsnaqD)^CEs~b)hKhghCiAk~4JI@Qx!u44 z5B!7q`2T^g;UaDY;JP0y8_qds((>>(rD}5*5`{_#whk-!u1F92*#5YW#1AA}kX!J> zG3()a+_Uq?ZvIiGkH=@1_Td~;4?phu#3R!qk@`5(FgO13PlXX5zCeNfaWPzxA3mL- z^SDVT=#Q`2*zte&i>y^OHIvfPY zOJZVTv>IH1j2^ieL_|H};NzeVJDNHR0U0SE19l(Yt!T<1SM=6UcXnxL+g-yT0P(t+0%~Q4N6wJD&x; zXD%t{8qZs9icq{}yv){)qovnzBs@Fk=G=jd6t(5b6$mOD=-b=cK+6Rdg3x&=74;Y& zvK}FT8SOZ$P)IYFVkEJSj^a#yE^52ze(zcWGj+C_qrsxxWuB|C5wl%x0mK}R&oLDA z!I4Ymk4jD+=uZ#sQRIND|Iup{5)=gDaxAbPyH!9%c_>zO0R1jAMXpu1a@032s`Kzx zc0k7|v7}w^w2~7aKMjhNfM^=Xgh4gi`XUxiR1@$ipTGcuSg_siHRd!f{7OhXw@1qY z(~_N5vvNIjU6B-Yf%IFz%pL$+fRw?afHnaRF0T(^U{U@%fnYDHZY#tuy}w?izo$

|DAl44N2257X&rvZkRhK8A=W*rbfV*b)ejeh)Okd_bpjPef~ zQ2rzjiWJs+qVRYZd9OA=GVqEP2(ejN4?rd1l?X2{!FaztF1RPa+Mt2s8q#a@3=3-s zfOMb?_zU_u0mlDuv60k!^-?y8_h6=s29)*HUIIeoN(5B({qKML!QS2;Xas<^TuDjE z*7m43njVW>&|$SBxTU3Kd)rK6sjaOoBqU^ie;gSTZwUU`!2^otld|&0 z)|T_t(FzErK|x24_>=Uis!5|)AdUlvus{~PHOO`&gZ>vlXS8|=no!={xOEs-{3}Fn zMY{N=e$!BP_SN7M0YfBkHxK{7L(`G;C@CqSa9W_O_WERl%XFxttBd-Wx+)+rFpd9f z+tQL4(6vub6E>%bdI|zrGALYNWCR7cGDLQ}6ZcDto>J0ZY4W{B_wnHBD2CnC;IqlNS#6Eg`c0Dlt5;(J?5p= zfq|k!O0S)m#yKmQESgM;71 zDFo^*->X^r|F^9^@K^e|B_)v|A-G&EK%t==7ZCxfa@<{9v>M&cHwTka3_oFr)&2eJ z;_CWuZcews1xKu|zMkeK&CRMiORdLsd1j{J$B!Q^3Rdu5jPf^0r-1SZt-rQ!3VlsL zuar-3{EYrvH!v}ps0*zH)ZPH`eo+1K@v*ky4?nQ{DJdysWJ}W0((dl=G>3xx{L4wC zeAaqYO>d?eoeoG;D2a%AhK8g(xg&_eAV8D=%3M>DlW$=#P}}1|!~Ne$Yk|AvFvDs= zEd#A3b#wr831sG=lnR8A?D zyFv0;K&$^|9;{b>K>_owpP!$eHaH;8&dy5Umzw~fGG^goc}Cwtym`hfRC}e>GL4 zDJLiQu9lIJ(b(9S&wBo^-9|`!yebH4PdM!Cl$G(Brl+P19Gj3qZ?>&iH!6H2{Wj}W(V z{}+@!0B{iZIgt+~aw9xHxTcfffX~g&VmssbAR-`OTaK*3Bx33e9 zO36VwjYlWm9;CPL)vtRu&66x#Y+5lM_2z+t=Cn z5QK;NgN?q}-@jid;lW>l1AcLF0qhsG01X_?OMu&?;12ip#APR>L!^WdG~l@Xmsxp% zMJ3^JP$5UxG~c?-!DuQuz`Wc3bhU-j9#0Xhwwd6SpXJUA`+PvT8_Hz6gkNbJdawCS zARb3O7A3pw=;)J|_HL}7=aR0jePd6(_sEB}A`uMue`1u88XKt>;>I$qNG3jWZizp( z3g$jo9O8RK`mRk1jfijy&rB$Pe|Dmvke=OGSX*B`tZ@mZ3P-ajes11|i zY5`Ry#l^)yb3wq>5>(&QYmh+%Pb7nji;HcIh=|yU&VkF$R9{;g9Dj8IPy;3=0U;qM zxCEj>ejy=4N1X2^y3LQVA0fizx1+33@_QGC+t4Ihuk)1Ho<@cg&m!@5@ej1^%zA-= z5d8B{St)dOr_P`H9yCqT3XLvAjvE@g8_(L?FPU;FmD!~^Jze|ND#W+bS0t+g0;NY* zTJ~jjLVeR~KCGFwj<{}o<6NaGlfKNyxWshgbDq}!Tbf=LCpn06 z!}x6K?Q1KDRX(I4^rbK_VNoRw@5|{nz6^53Hv;iBH_eSgSD7~A1i`)#g`qC%!6F$c z;ikk=?}~fP6NMO?V$Z+qXI$OuG+DCFF)CoqHaWqXfx4Tjs;Z5R4RDzj78d%(Mio3F ze`~6!Oo0+%GPIyV&;|h*4HwjDc(t*K3BPB#bSe@|QpVi=M?gv?$ZhnOL>%4P%&Y~M zMcTL$&g|J7`5Yz=&KKif@R;`=QSFfCP#wjIU&SoSJBN^WELfNQx4i-zzdTpp$iYAdv)(5w;{(atF zBLw^C@x|6Keg#I3v|d>%Yj8-*ik+NN>gw%e4;JC! ztOf!3`N=TL1Z_M>{Q3A~2coJqq<#0PaeA%e`%(YY8Gpw`b{aTJ)QWls=-e-ZrGO#x zGS;fuh>-2K>?LPhGlbl$i6;x^Zz1_pxRqhjP~^H%X3OUo206mx@+cr1s87yiNGj@Y=GOXsk41QumehIpC%g+@V%HNZ zEXRGKAn@p3q}q9*g5K|#Z^`;RZwB?|e#-wI+Fo!EHH6;dK3U4NJoaYJqef`|%kFUO zn|8;3&Cu7zeE)6Dg?}}4e-0QwwR{WBombyG6 zOK*fD{GUg~%>BFd@>q)D7h0aS#zO{yOEmha%~dctE9#$`UY@&AJ?yG3154z$$8kP2 zIPJVQO%dzr(+XcRd)#e$R*kLiuX^t8Jsi%R`?c;PF#NabsW&h&v6QL_sEn*Hyw~if zn?20#>uKfWDbRJnB=UtN;vb%0W49`thP>M}9_&@hxZ)h*(&;-P^?5G zhv0TMSQWXSoc6Y+{zlMzwqHxJd2ana;1x@Rx7zNlX-q7|eVzLH+>E*>Q0WbmrOi;9 z#B0XS^Uc^Z-pvgsr_SJwc4FK3F+hH2@_7~=dXs|QAnK5veONXFY_=l(D(UFx- z(_jaiYLA&a9*U*j3kW3mXd52LcA?ZUV)QG^wq6kg<`Ff zaIGnr>Flr0y61~|Op)VW{?I#Vd>4aGM^SbN+VykTEw9n2`S4cRc{@&jdA<#g)8y^* znmy*rYnR;LR7KvU(~LXYpX0?!jy8;32dzlg=vx|<%aFf*w<7S+1_sr{l4_%8a7tyoN}JGIxLos(LT6lq0X?`ye`k4KiSj*=IKXS zvz@zObq6J>2wsJ2R<}nXevD^cc@{?_)8^QLjpwW3nse?&HnfY6Lb2N9y5l-dIlRYP zR}1~~cTH1!lH8&k^Ukc(UZM9%?;^JhY8IHDT5ji>?^*3`TNGKbOWR4IN}D^%!ip;y zQTkB=Zen3%ild)>ByJMMqkGbV>Vvu`t!UxZ#b{#K>|xO1noEEb94@L-LKo_0j|_4fz#A<w9Xb$j)Q&O%yws(_ z6AEQ3T6b{*!%Vqe>5Uu=8V_o{%qK|Nk(JRpJ2~2)fFg`umr2 zuRAGeI#Tx3i?XpxZ11hp;K5z`V(SXL(QL{av>KEq(w|!lIwJ)ES|qvz4STaFW`ndF z{7w=q(%%Ey#B^6g?;QrZLZ*B#nh5qsrB%HzVdo_lbNv!20CCQYy*_hgflJ%lwuSAe^lxoaMIYi(6-HbQ@zOw|i* zz)jrau8u-^85^kker7Ai1Bp2VxOwabW=*s*K|$N=3!Q6*5W_M3Fg1sCQ>$*vn_DJo z>O__Q+0FS|2UHYOHT;v(L_-LbiyznX5Q>4;lEYhhhD@6iUWr=`pqlu3T6IIdal zPbpK}zjc%F#Rq0o=!e2V@_l75Bpg_S13au3zg)A^PI~$GQjQJZ8#+nKi282lM9A~g zQbq&COGSBu09L2a>HIeiAwSK)Kke~0kU7emf2xP_CFfp@wXZ%;u$a9Q$CUWE05D{$Sz#6YJ3<+q`%nW{O3uX zvC-)ek(=c{Z{WAIy?)t3^rnl(UVvI`Ed~0fv)QH*`Ld~;2yf6Ca$x7FC80obcH{H& zLF8qu;yVaURR5fNl0te>4kP!lt9wiRo~I7Rus!iy4D+)&SMp@ zk0OGoeGwcOR}1shY;kWQ-U=bDFrQ;a$7>*W)~qANmMxeKDXZPEmZw2R+0de>e{!=4 za17z9liQ!PSxS?dA*gZAdgi-@YsWlbKHn%BAiM0Z`6psfN>3=KD1#Z}jjfkFN)c>F z93Mjn{vM!e{2khgm8ofFwH&bA%?E}n9#R(OXm1hy!x2a2GJuT$Z*+zc*sjv=L1z)~ z1A$wpznLBGvVJomfqqE7jk|Y&?$8G(?1bh3;v--L{eejrA*fw7>o|dwc!yisfWGFx zS58q$1R;rF=}YboZ9C!zwX1SRX2*$=_x`djryL{L9;FlrA>4<19b#!y1E+btGZoQr z^L*Vj@Xz--#Kq{yjQywA>v}Vvw_?_^25UbsOGp@H6P$7uCVrp-bRBt&NZJx2H3V5`bq{k{oQH9HREOEvZS*aM56676}k_20ND=|-UG&GZ>=jpfaMrRUj zdh^9{gZa|i8p{KVinwu>;;49qr)81dRH?K2oL9TQ)>rK?)@yT?9E$d}p7)0B^^)~- zHyX!os>}edy0*0h4ABx#(()PRweAptc9d7|gO)%f`rsSGr_szPN!#SCF5Ku7TE~L` z%cWwsUQRDzcl(0TB)vv&0E z*^}huzgzt(ZQ!*2yBM#B*$%s z73*^gj7W4Ec%6ECN9QBy{)N=L4*kPGRqq-QxrQ~OUW^y+{%uYF);IMQ`se#zgYMnH z&ii&#a)JA&>U`BR&3_oXdCu#B&t`SHg-KuBn*wmD30snC(OKaX%X(FpS%Tk7O#ZrZ zasR%Hu9?pXiLHU?URFo@*(<6#2Tm_OCd1IQnPCyq`l1F}d58Bz6vTZ+vULvb5UJ)T zPDAqg&Z5<6^#cc#(Dpc_EkSNa(oK{48w|?tXS3H!9)ykA?!U#s9uyN^Tb_HPz#Qa- zg01~F1+7msecqf73R5aCdu8@)xurY5bu|+=#owKFRJybEEV?oLfjz5xzvHd2*a&I0~-I6h^gsl8YdmA&ZXS ze@X5n0~2K#mK-RghEuJH57-d+M^A>@@Ax(suK+b%dy=!WX_448%RIY3us<$9kfB)7P$8V!hriqLE5P-F|VT zM^8ulRR)jBwSX62Q^5RJiEMoKmlQ<1!-Wx&8wJ5Az~QI2PNfEv$u975U%YQ?b>7!j zY!ZjgFoI~Q=EL{I`aOv!ev?PS@m!oq)F%t;N=!|RX78k@BujoWQ*tJEXh6W52oh_)tuAclRwMzc0YZ zi6{9M6y*(sN4?8H*ptEdh@#JYqN4QIbv$?^H0-e*0x=w8s26wDTf_|u45?K3=qQzo zgu?v$+5=>lY_3~3@^6qYnddF+&CFbE3+K8#Rf;Tbe;ApqdR3wDVq|!FD0@+S7tFX@ zK7ym(i|W~6o=+3NX(V~iUpF!?wAP&&rRxbolNzc}-2Qg-b4p9C%3xZzyl2RhJgbJuq5+%x4nxaol9C!y~g2!0+BQ!vRam^le*6!;jpdn z2#_*@#o?OfRQ6*$94eNQ0RAfj2rZ5O_7XQI=Z9CM1WUd8K072E@UCv_k9X3nJcNWx z+>U(Vb905funVnzv*8#HxQ|aa^cP@#P!j-k2SE`ZU~9Oa1V{%224JvD5_ImP{RCt; z1?&HidgV_aKLS2wg!ex@cn{qpVgA5b%RZ*N9yrUS$6VJRaYs#cy&>APu!<~Mfxe91 zJag8k!}iC+b-$GHmPAJKShC*l9oh^RhF}adJ&)Sb7)|CAepM*^T0(+1?183#H#$14 zG4Ufx8&6Edx*$aVy}q~`;VWgwd8T30gXFXjwzJPFNy+OWJy!6L!lPf1#2amK)7bJe zs$I(6f-mg-d9n<-@)i&)mAD}y@%HaTakq6b4Rz}`5q$u!ZNOWU#wm_Pfrx1zU5iL_ zzr<4#tXJ6MrZC#={RGRXxd$5qkx}8Lt7G&MDWGCzmQR_yI@-kb(+K%YHYT$QkG)2R zu7{`58e=Ev=<|dPq?g@i{ABu$XCs~&=Xr_Kh>IYmyekDn5Tdwe-xvx8>8?XMW`?L; ziQ3G(@?OeUr%Z?i^sVS%VF*5&DdxVCQPbR^1toPi@=_UO-$Ie1G`s1vSn*^O1X6%G zW617_d{`Kt$JL2a#%l%u(}~SnWA%2Ou-IGk@Q(XgQ){H!Uz($(C+A-n_B?d1+s;e) zLOf!?&#R@jQyqZ_>)lqpJ);b#Oku7DwJ z!ccdJ<^m^)`!Unt1{uV&yW{1xJIG0ZwjVwQ-#|FFv}1!|?nmgEJM^77|Ce2*wL>p*}=#YTF-A z?LbdqQ&r#TKlX*Yh!&teFQ(Y}JXVujG4V+R3nspnffy?5)vnRNjiYFZhy8&# zFpUAKlTM2V`!*K)`D|8oilaZ5CYK_H0LzEZ`zI5t>RdaZeBSsF#yAnl-I9l+4eWRvu5Z9`NB^@xbAozQetB39qZR= zoMtmTVW#$~taTbw4k+!Rh%Yf!z8X4N$Y+lEwZ1de-CLNV=0wf)iOREP#^Fntc2d&g z8?{2O;=dt!t@N5l_~mAD*+PCbZnE5r|6RIH7!gJ1C&NrNUkGG^PG48>rgF#<4`?VOn0qMA3=UDClfh}Ds+BVZgv6Sgeon5Cqo zv4_p)`aNC^Q9Qdx*g^Y(=QuP!|6JE;a9{Id9+-lqDkI7MnGCx-qc zj|4n?lX4(-Xin98S85y_|-}8DfXFfp{Ow-#b+?$!CT@)#*U*YHPdMbG22G zk5`X*rn?Nb7)CLdPV2fi{_VUk z8s{W63`N9NkwigS?;X+V2CUtF(*;h`4*7mpTU=_u4`FF;K--RAmFp=eZtpY9`wtCKBZ!@Oui093qzPOq&$)x8Nb1e4pSz|-E<_X+g6KUE zQKXruhPi}|*pZ`+AG_XH6t&Ji%k($eIgUe_0Jr~lwWY6wTjCb-?Xew*{A|FDb30E1 z030GM-&yGVQ7}v!Sk+lTWXVYsg)n36?n1ChX{j@I9y8oVQXhbY^p z-9-J-Yvrb9So`iaYp(&8?!+nC1dMFZ$2;|&XtW15V2jPW%DyYpTl~fCUDtH*hJJR5 zgz}SRh!Auxe>MaFeiM+2VAle=CVvnp*&Ms~GsD8Dpyw0rf70IOiZ=+8oKKeJ;!9J< z(PYNy;TU;hNt!=ubQCA|BJMk?@F3H!s8|wDtgpzxE1ELXRpjcQenobS(;{Z3i zj|F4dICNA{Y|%k6x({0q9=+(V^SKR^JH)tlXoOgxwC#KSZE^yP9+sqs!oSbI3vDA& zTY}~*DR;#K;|1@DrHR*SXHT!Q*t}bNMc6>ux(h6 zNP~+7NzadSomgTd^mqwrLrW?|a|<>G_(C#VtZIkpg*=(J3>s_Kx53-W`n5w}Y2Os* z(N`lkeAn_reMu)gIRWOH4cmS{?)+=>DFaQUjLi;mxEzV2BEP6M4NTpFjvJFnyB*+* zL3>Km8%Bp$&7wTRM>ua7ajva2h`OPk+C2GTU44?5Oq6YM8=A#1k27N8@yJ}f3z@I<#UwJA_M zQK)RDzltv0@o4hx~qO!3IB!qr4!Ff+l%}xAU0zd;N0TQQZ zb;Yv!Ln{!LE<+%6x1u)j3~9MKRjMS6i_}whY5lURmvk>Pv>|`)gJjaXA=ED}s5pET zv$r|KJPyhNFUc3TFS`Jzc!H6(C&;ArSzegP2N8H}Ut5=pO@fz3f3`uWmE=L_aR8&X zbFtR8vyi75TjrY-H1`)YmR!o*7f-`xC?4 zU=%aU1a5B?kt;d`#0TI8RdwDU>`6A!WcINz``!Svr@aTGCTVSh z8j!D`d%8;uCxB^^ifD9zY9cL*ELI)osYT; z8dJqJ@S^se)KPhB0M`X5W>yi5o^NWt&+BGZE(xs<-rh-kTH=rUfn(5p;noRKE+eQ2 z>#=@~S+f76;IHJY#&&*%1E%PW?D`z@Q>cUs+m}8C^Z8877WjYhg3wuPA;5w+N9t67 zs1-Q1GW>REpraCI0bPPVpmtxwbuj>52NmA^J7h0t1kg4a2na_YY~#Id^fKMYOGgg4 zS?wzhzBhI;Np4kXJ4r5`iumon;ySc?8W^RA*Vm5e_mApKke2*SN@e~p5vnvjJh}JM z+KTcoq9K@fs6xJ(_ooLU@9}YFTJaBuU5t})?3XFN(2JF&g^Gqsgg1sV*v>f~z^WG6 zuS|i2iHa91U$;CYWJ50*C5&gSP&(g z&uNl@=THGf@4gUiRmHSWXHvr3!h8i+sM_I zMXP0L;k-#3a*H!@#hlKo(zgIZ@IZ~j|08^8MVS4cu#i{+=Q4Z2wAKa1f_it!Ew+(+x+TF-Amp6YS*xv))&dG6-stRExfq&vOw8B49GB<%jIs>G6XcCWGwPLTs`0*5j&h>ai(ATM zI1U5br_mB{QcXM!ECRyv zp!Sd4QBI?25Do!M@>cQ>*E*Q?Eu?MYDvd38l_6C4BD+!Br&FKle!3D>6 zp2u0O+gtAOcg>W=-V3ACzWzGIV%BIb?*33Nq7a}2@Y=od105k6by{>ni%3EI==kw1 zi8ta?Jbfs*BNsXw?ST{M5QG#*98JE454_Nf6~&m7WbBhDB_JLNp9zC&;4?Zt7_$I` zR^uRo?TI8;cQs)Sc*qq3^SApsZXaL&u7q&0!#K%_3%5LgZ$Eq?=Dl%OX|pK+ zAa2IaqfXvP%1s#^VFdtN>`$?hhUFI4G>tD}`V#d0#z4~8>ezel!|GkYQ2t8P3P?2r z8yi#z0@F9u|F)qw$Z4uL>NW&WEcElxs+UG`fB2D9Az>A)BIjoCRHsS5v8LRcef?Zd zx^5IXNr3@@tqrn*3?8Z@V2CjTSjm%cJPE0eKD0IYos57yYSd`d&#vVDn#%)JHye-y z&&AW+r`M@v3J*h%=i34kV$n5%wA-BwLp(xAUU_g0Lr}GXj_5>DuAJ15(u3H`q=E12 zfZ}FG17E&`fA-D$qBAZPdA+i5SFvMl@`?tV%E!f&`@)BW(%(V5DR7rE@`Pp{2>@-TR$Bs zS=7Qr!MIqaE>8wF$hsbO1`i+ zYZH|BovK$VDy2o-fD0QCzb{qP(sE;}y${gCcR+UBR&%51x~UaK-&V) zUHRPA6%RYF7IJ-vAiq42aLCa>D1PDZS>1X$sr?3$qa@Lr;N3iM3WP3R{08c>?!;dE zyB75d>!FC>%u&}Loa^{Q_rRc@u#3gbhPoJFppK^$KTGQ}<3dtqUtSNjYs0MYS+CX9 zCN;uh{AqhK!nJuomgWh@j+!-tBO~BW)g)nGUL)`-&HqyVl%_JL!)EZ=b4%NAY`$(C zB)XAGK@vpxu^+Lov;w6%Th6AFzPRAj?ye0X^99y9m5RPTAnJJoQOfu)r$(<$hCFp?$32kp20N+srCv2Yn#KRXHnSsAO9xRL_lDjNV<gfL(eHFP5^`S6WnMd>TGHhalgWokQ5|RCRM$Q0XI>wo3?hz5ZaaAxViR{1A2m- zHrF$@R>S1hMsq@8vz9NpU~;TpUV&?#vLeD0ukg|9ZGa99F>s;NEqeAFddoE-k08ZhvdB|@hOtq1rO$5nY?xQ zMRorkXW{kx{Gd++|95S#g-3krUQ{}45ZTZ|S&g;N?J~`%P6D(#b6*bg7*RD2R?`cQWama= z1m9&2N-dSSF?sR?WUp%k+e)cAU}~&yPDqhJ;a$CNcnz634d{9BML7O`jYX z5QtF09_nvWp{Oa3$J8Tai50*aKHzL~n6FHsf%-ia8~{GM+q#c8F)Z*?*_> zEob}b4Vm_K)&zy(r?kVLc3S8W4B6WF6Xa4j(iny=f+l!g9V+GdYZ9`bQ1r&r#f{G~ z%>~rze!m%Iu6Xig#LT6Lg5}LLs+xazh-os5!Otm&zW_-GDOZlaLXaXIJK>%S zN0L&8Lg1;(pMYY{WxHtj8E&}#FM{e5r;dZ^n@}HKdXA!L-it0VDcOX$zO@gY2M!zZ z+CsEBJ0C*@YdGLYiEZ4*x6!$ocDq=opBp3C=gbPd(9;`ix*?HFIXLHK{O6 z;gB0M^I?~8IR8+Q^#&!7c^fHGeljbtUgm{h_WWTA!XMP3wP+|%QLT`gKFj^I_4_4* zqj7@%$-&`pM70EC)PSW8tN3>n8jL<9gwo%}g$@TXme`2Q%+6bkv9efa=(SHY+&`x@ z!C78ErA-U4qfkzbmg4FHM*(kZF3TgsnQG z;p=yi7}qOf^pXLGD~v5@vPL!}yidmHO-f3PWd5)9pLTEM&Ot^gw7|pBn7C%7un;J0 z8O;+9myr&)o*Cu)sO123s! z{uM%iTBl|5j|IFZBGQ(4fodWzcE2;6hSmB0zSP^HtU;YT3p#cMH>6flcM zcT|yvs}#d{5-v}yJ-91a@~QBS;sx~_Xd<2hif`Oy``48Vcb?SzCE=a*>yk10SNP>};%T4RQv)4ki{OuL z)K!U#%0k~fYsh>g9XLIe?krxfp?+k!>s`QU?KR`*++;z~>qpGMWFnZ2nv3OAEim<@ zZA6h_FQCcW(|bxH{hCGqVb}h(2url3Zh!3`<~-jGE{U`#nCl36u7qMrthX`h1N4tFnDg&%%h<#gxzYIj`d?T{AbJ1I_u- z@M@)ra#d-?22>$C_Tz2hDm$J{l@j45O@A!qv@5}E`ZZoFcr~qgi(Z9U3k?%0-XJvH z%s_4(>V6Z}=l@*KV=GyL^g&iA8y=loXRsjn?$;o)-JS`1hwN$2;=X?DJb5@g5jk6~ zycl6Ud4G7wKlmsiGl8VucGi%|cbRMim~lE8q-!ZNZdh@@zamv~VXWAl(Rs_4Y~Wy@ zrTUGN_^^iM7cU`77zq*ioc|z9Srt^4A(?omwKJ3fXQ`xXC5XUrV^}2{vs3@X4ME7i zj7iH%OZxeE5eFQaB8AVOwNk`IC9+;EyGef-ddqB%M7Hfo$JlFiAp!)&8OsWTI-0wa`4B)y7jMS_ZYJpt|y2KD7cjlVTyr(4JR9a<*O zfMHrgHKVVmB8fSWf8SCqjL;VLtc5@-7NF(<&`jtA_#{9|(GEVJHtXXR?ll#P zp1Nti%}}zGO7{ndoIV^**+<#}g|D(O{4r=P{)(s#af)tSs@)M;8X#oT&N|h22fsoLph9U@x^d?0>iu5L3 zLa(7H5(tR2(2EoWQHp>df&>LA=Z)X}=H9t8=gge@-<^B&FPTmDe%D@WJ!?JdS>RY2 zsTX%1a|bxxy9vva*w`BInUdmRCcP?S=V^BgraLcS_JyzX4e`Vv1r|oM3FS4BQ%n$? z;AdZ_1SZQN7Y-B44(h)%HQxlqM>VpN{q)6zHQd;|m;cNT+;AJR;>#IwoA(pdfb3oCk>`W0!eHwpHo3C><=w5h`|?W24$_ zA`m-Oeql>SCi(mqK9h)Z7R{t$uvapC(#t%r(w8B2-)wuqpwT3+mZAw6@(BI- z7x5Alr!EKWR{59j^zScZba2T}OsA;ZFD)$C5iYJoS$<`So%;6+mx7{nNWSIXHj>7o zVWmL3o9y3~?BVp0!<$M%WVaQiiw)lzpgi<_2h(Lg&cT=Ix$WLJbLJ4;y5aB|vpy(+ zUJor;p_>eL>DN=XO3HVy#tE!dR0$Z|F1D0DY_kAV4Lo&}>cs13@uwdF70aD0vOi}84!JuAnW zZaRh0uo^Nyjg{LxD5yVOKrP#OePlotS z)caneXf=YbS6fWGB|SC4S$l=Rf6)fL-O0vxj@z~+#Yicl5bs+kleVOee~0C z`s|uN=Q*=HDYNV?p0)8C6`Uaqj7eKIki6&R*}))&7q;x4+!zoTmJGBnJLEGDih=es zRA_WIc^@8)RY zVgUB}lJMdr2`@tbHxk=;-a67DJ|=N>n;d(uH*COWhSR z=I7)KCP$r{>h!hC43R^FC=vFnmJ>cr++IycP zKRy5U_7-o~?(+zR*NpF!8&xOsy$`9QROWK_e)xR8Oa~p`9?st`GiP?_qS-f$pE?63 zhj*uiL%uRHDJ=kWThwo{$jKD)r&J<$fEEq*gE=t8+u8OS*6ST|8lP!)rF=`%NRfUm=Hg{3B0ZEt{AKpLFTK-d?{;3UrdJ3Avwt$Gv+*SfS8yR z-Tm;*d`U1+WlQhJS+)5`3{_|GM!un!2oO4iN76rhMZFM;I`2~b+zTO0pWia5T=cX1 z{_!=~4aZy?{*HHYsW(xyXhHSZxT_*>fxy|_yLTm;H%$Eshm-kr!GLY4@%V$f$skryPMn+G>RoJ| z^-`n>;o+lQLnfb{j;M`QZc0T^`rI#(GZ?rh)$zIyN;P5gxmrfi4Olq>C}9iM@cN-+Rb< zD4cZ?(o0@!=aN3Q;2grbRj$c9- zXw-+zH*7Bk46+iri~TW^WqmiEPAyk=U^J9?6qv~+^giRyVHF@;^JEEpPrKGIo~TjK zs~!J6eTNW#0TdZ4!3Le8JU_MZl#JvryN`|!iMs*b4o8J)%$Z;V+m`{8+|nnJeq9sP#PG1bzHbrSbISd*cx#W!!CXup@?`tfJ`-acIdJO_9x$q14yf@t(|xPc ziwMi^(U!>Son*V|W%=^aY*<^H;usm0g3om9(&t8t0FL9tn|e70bRciKNO(kPbL*1$ z^HMo4jN+&lwFA6zgd7LUqiEGPoseCbb@jgsKhPG^TWlkb&meM!*s(`4b9e}7@)#u@ zcUOsb<*ZwFI$oyFsumD598;2)LmEztNfjzUXd6zhv{n1KP$R~@X!qn#?@oxtvP@~4 zj4C5qvtav@KofTU&WE71AepUinvm@A@1Yc$; zef9Wa#^&yJnIVLgEgKW!u##`$IqO1foo&Rd6KghSd_yXDwIT1XlP7-$Sg}M*V4u&hBi}d74%i_k%Qw`Y-&91VeF)Ow>epPO=_H7hJWe#%Cf8#dkSI9 z&C@%>4K;YL!Xu2;Q56y;%#C!GrLw`k5f{yLH+}5fcFKuXY?6Wb3 zE6I>nqdD-G8`-y~# zZWvOYLcU$_C4*>&Yc6G%RZYIFIN{?3Zj zdwDgsF|!c2M^Mbt)r$g*p)z@91v_$4bX-ELs~=&>BoH&bcUirlvFm-c%1ZPKFznb# zXPtDJQmNc7QMk1k9Y}Kxw(vUGYmh{?6R}UBVgtT!%6C4JWElK6Nv5m>F>jY;5sh`d zLmLto&zYo+f3j2KDs$35t&gJ}D57@eC38q>VkGz1!n^56>blQCi!ET^v*u9* zwR<{tI!pnA@Kq7`RmqaV9r$lnG@-PG4sVwC?^|%pk$HoicyyubjHc#NIVCW}K9r83 zfV`=^6*oCYqdG)3$Js&hyj@wyTxoY%wh$)GNfnYe7nbH|3AyCKnn7x`2qcmrnTh;2 zf?=%-ET*ky%#cM@> zEu+7#sOEzgyIPfM?(Y|?+lsO6hu16{C9>|w!7+h0CB5R1oCT;&T2DvR?g1$y+It0( zEAEif@s zTwkzSGTeE-nP~VNUyyZ;4K=pc)GpNp*S?eVbir+sEu2A9kPH2(`|ag7F8+E`+IoMm zH^t+>LfWUjTVn?_aWaRI|DZQRYf|oH;{7PS6va;2`Zql2m@MxsFTbQKVIGcefKSX^ z;biN--!>X~$NsT=w#*+hYmTD04V5{FA`gXVOb>531S+a$<8HBJt#%+9)Ybu{6ks2K zUGddkZ#rXh@RgZG?~^g&c-qt|nGX@;owPTZ-+ER2fy0Z;8$LB{dRh)JQTG8JaSoTG z6o#3IGH1V5gS~Dq$a`h-CsPo*R?VB2#K*{9TLi}aKuSlcmFr&zRiHAJPbNGP%bj!e zc25J@(8L|7Ar`NW$shuOz|7Ok?J3rMiMU9Bqvg99Qa&=_zduupS{T^LxKVbDDu~(_ z%dg=aA%l*2x4JOkCp<5lW4>OC?`aYXaz!2`U=Zt`68`ls8(UR);s zht!a?p}SA{DtRs9l%HiMx4onynl!-{oPM0!owFR5BA+SaR=~#9Og1G#;-<-NIf@bT zZ;GNhLMF2A-NMitIo8(LbKm8qB#tpH7i;#K9e)bbB6KB$k0ni=KpCs@YM%5w#H+%I z>n?lpcwZq2iP61Lv@;?TuPwB$REys%chK2G;g+802{iO&UjF$~XaiZ4qyWib4KS%n z%lEY}B!!CsDM*eOM*6Kyp&*6~$IKCR<*}$Itcd zC_P+_o0YyLksI=S+J4-P20vqMU|>MJ{PsInd`H1C>K~-CB|muAkV=+SPg>DW7(hX_=@YSk@&lz7x_T0d~_>ywYW^xoacWb8xPJu3CF zk(W4|5|w|ydtVlwfAx)0*7|G~b7&z*+uCva$#2VR41K0fJtfZJ3p}f;WGm$$;hEZq z?fGn@tkSk;AS=sKLKg5to>^Pi9B9&LpA=z71;A7gNn=y`Sx;6gN5XWZ^z(EhUESN6 zUPj4Rd6gv@&j9$R*LeSftVP+-!Z#7n-XrXm zlf*rJMV<9ehXdyfoR8kC4(%+AcBZ1UCOXvBu_U!VIDhf*+)?-jE*{j>y0M$UcRk&;nTsy5c__b^7n4OvkMIADWMMGdh3*lyPw0>?Ku? zcLEk9tO!p{PX~nIj+1vvSo9u7-RF1ts!|ANTTb=S(G2LORV&M7?=G*!TMO}f4x@X1 z79XG2iKG#lmF4_N7~AIv@?TTP2a0uuvmlo|@C%%Cy}&f&Lb?;Iwui^IN5~&@3nGva zGe^O~l_D0tR0KgOk#iAZ)Qd@B?eCXrVcUmxK9lkeV@qV~asi#kYoY}EFW>&%uFM%T zxMunFCs>@JE>k&ji`0vCBxxiR%>re9cr$7H-Xk9$QC)a6-o}-=qsxvf^n-0$xB1rs zXjU8Q0w`q)?k_Gbf<-hxxDTdkgQrGqoZT$~;NWaXBnh z>T6V|x(ZmWqiuM9_=P;!YK<5Xa``B1WTp_RpqmcOlJm&B+iyXQUu)O80yZDWYX=%a?-#&@QTT%kCoH-LC&eD*3Z7(FGU^6^kAT(J z`wUR=sRQ%M7BARw7PZ+Ow&l+?^(pVsYO@~k)klk7#G!GelH!)&ppJG!?L61$S^p0a z`w`UHER{Cqwrrzr#oI_+z@ZBryx-))*Ej_Cr7fOcr~*mI!wWzo3!E1klhY1?F6^qK z`~rIuc})?cnz`kA{jM^EsfhkT)jm;q>||1TLjws78(z9&XpI05Qzo9L?#__V^m=?M zKzEMcid($eji-a1w?)=As?%$-$Cb^ALKHu)YbfzP8}hy$bl><^YBQFX(_t}e7gntB zi?Gm9E-gm!<9J0{5HfAidRi<*{5AYz_0G?rn>h``;V@t-zO`r2wc6@1T~JCIqUWQ? zv0BqxwROlQulwf)w3ON*@a|Lota}i`yVNTe%IVCQ+s{gGK{AIq2KdX?zf}(QsB37% zBuQpdjlD8@(nFsac{5tUru7e}he$ewtCN%9SHpU1zKvqj4QB1>V2=7xlhJW=OG}f9 z;9c`!8XYQGzx?HPW9uZAKMks>8vbP|B3@OQL>`yQG}N>g_BhfLGqrp?$TR#c%B@t5@jiUoeBqpkA7x6{std=|mGEDigohI{@opa=~ zRhgSIt(0cIsbt7iZEr*>tx+C3PjA$MZ0b#sicj0!2kfE@|jI zU!N!UcAspwZf!k>2&inSOjdlH!hkQ(+piD^a|q0x*%L>iJNGWX{1TSjP5+0*_H`=~ zhiX0UDpi^m&up(jf?vwsZC|UvI#52`uXozzMu%T=2qwtcJhOE-AW>`uz}zMJ{qt#l zzOp`u#Am|Ar0&rt4k@4mYzL7IAQSxM16oCHRVG;O5%o_3tQTb-LzX>Zu9}oKI7`Y( zT<&EuBt~752KsO{qBP`|_G!)k-}(_8zl z=zIz56H!|Uk-e|;YrCJOAAi46-?~zFj3$>Tv)&QCTl70P8I;bd9wJGF*1r!!HYq~@ z_(2vACYW8VA!w4;cQ*>46Xh^H0S>jHS}ST^qME!1SDu>9OUB=zkF2_P))LcV=3pzzhY+Zd!>+}OSgwG|F>Rs|ByX+z6|g6lFa5TMIq#$Qvq8|i zNUADd5kHKiF*wzi16?D=F!gg9Ldp3*>}9Xt6Z4#vTi9$Iv@Bct_DwV;?ROn&tbtv@ zuSw7))@9eSAif6LL~Rq#^Lz?WQNqt{Ff7T;2Vr9*#l<~984xKnLM>kh9zfdcrEj*= z>Tu#lD|y|1&6D(njg5+I3(vMkV$Op!UKfz5z@VPc;f11pRdEKye~;dvNLS}eBV&hIUA?n0GjHOgxxCpcv-K!vqzsq(o1dvB_ATB`G=K;mJ%39(uM$R!tmoHp z{%9fl{!(buY0K@u;Wif7a(Yq>c@HgBV)~dC;ma4Ogv6cs`<=tX^tl-vJy1f{IcQv{ zUbACNwRuf>d(~u-DN*P`N2Ol{Tms=v+E(mY~m+(YP^YQSZ( z`m2?hV5?`cSmM8Y=?0t)XTx4IQY$Lp!(BhuNpc#xdY~wo)iu zxbsgJIJ5yl`0~ouvDj}wGE$6(o=i&2sj%KoEt@=rB7f~kNkHR^nOy^_(vX;Fz(_1YtxEo3 zP@<54C#O3_^wAV?#`(1On@5%|9~S|CmCl5Y-M*Iei^V+nq42oo(g76RVTX?TWDYWI z6Vks<>D^Y@qr=1eKsV*Bu>I!>OI@2+r{s(Ea!!bJeUjg`Ex@=QPH%7_l4M)SC4zg4 z8i3a6?$6&OW?=Rd=^6?VRy&v~%?D%5>}X%qBGh4T!-%YNVQmTUGZ|ECkLVVt2l7;i zUwehp_PDABrGDzn<3g}UQuA4mk%3P~t8zvXWY-r6)^@@2bFgP2u0W&5j^y4kKwu$T zfDS(qNh&2diu`RY>1t9PF85w>7QXMK@*+8>j5p*h?w86XZC!1@Cvt{hVEO7_)c_P| zURjcS<67ZgnFatp|A!R9e;*%^c%c7 Date: Tue, 29 Oct 2024 00:48:45 +0530 Subject: [PATCH 2/6] Working batch inference with gap --- ...casting-function-gap-batch-inference.ipynb | 925 +++++++++++++++--- .../forecasting_script/forecasting_script.py | 9 +- 2 files changed, 773 insertions(+), 161 deletions(-) diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb index 955a89fe4ad..7b15a894541 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb @@ -20,38 +20,9 @@ "* prediction context: `lookback` periods immediately preceding the forecast origin" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "1. **Model** \n", - " We will need the MLflow model, which is downloaded at the end of the training notebook. Follow any training notebook to get the model. The MLflow model is usually downloaded to the folder: `./artifact_downloads/outputs/mlflow-model`.\n", - "\n", - "2. **Environment** \n", - " We will need the environment to load the model. Please run the following commands to create the environment (the conda file is usually downloaded to: `./artifact_downloads/outputs/mlflow-model/conda.yaml`):\n", - " - `conda env create --file `\n", - " - `conda activate project_environment`\n", - "\n", - "3. **Register environment as kernel** \n", - " - Please run the following command to register the environment as a kernel: \n", - " ```bash\n", - " python -m ipykernel install --user --name project_environment --display-name \"model-inference\"\n", - " ```\n", - " - Refresh the kernel and then select the newly created kernel named `model-inference` from the kernel dropdown.\n", - " \n", - " Now we are good to run this notebook in the newly created kernel.\n" - ] - }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 2, "metadata": { "tags": [] }, @@ -64,30 +35,16 @@ "forecast_horizon = 6" ] }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [], - "source": [ - "import mlflow.pyfunc\n", - "import mlflow.sklearn\n", - "import pandas as pd\n", - "\n", - "df_train = pd.read_parquet('./data/training-mltable-folder/df_train.parquet') # We stored the training and test data during training\n", - "df_test = pd.read_parquet('./data/testing-mltable-folder/df_test.parquet')" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Batch Inferencing" + "### Batch Deployment" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -100,15 +57,17 @@ ], "source": [ "import mlflow\n", - "# Import required libraries\n", + "import mlflow.sklearn\n", + "import pandas as pd\n", + "\n", "from azure.identity import DefaultAzureCredential\n", "from azure.ai.ml import MLClient\n", "credential = DefaultAzureCredential()\n", "ml_client = None\n", "\n", - "subscription_id = \"72c03bf3-4e69-41af-9532-dfcdc3eefef4\"#\"\"\n", - "resource_group = \"aml-benchmarking\"#\"\"\n", - "workspace = \"aml-benchmarking-rd\"#\"\"\n", + "subscription_id = \"\"\n", + "resource_group = \"\"\n", + "workspace = \"\"\n", "\n", "ml_client = MLClient(credential, subscription_id, resource_group, workspace)\n", "\n", @@ -122,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -132,74 +91,47 @@ "\n", "Current tracking uri: azureml://eastus.api.azureml.ms/mlflow/v1.0/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourceGroups/aml-benchmarking/providers/Microsoft.MachineLearningServices/workspaces/aml-benchmarking-rd\n" ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azureml\\mlflow\\_protos\\aml_service_pb2.py:10: UserWarning: google.protobuf.service module is deprecated. RPC implementations should provide code generator plugins which generate code specific to the RPC implementation. service.py will be removed in Jan 2025\n", + " from google.protobuf import service as _service\n" + ] } ], "source": [ - "# Set the MLFLOW TRACKING URI\n", + "from mlflow.tracking.client import MlflowClient\n", + "from mlflow.artifacts import download_artifacts\n", "\n", + "# Set the MLFLOW TRACKING URI\n", "mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)\n", "print(\"\\nCurrent tracking uri: {}\".format(mlflow.get_tracking_uri()))\n", "\n", - "from mlflow.tracking.client import MlflowClient\n", - "from mlflow.artifacts import download_artifacts\n", - "\n", "# Initialize MLFlow client\n", "mlflow_client = MlflowClient()" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 7, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Parent Run: \n", - ", info=, inputs=>\n" - ] - } - ], + "outputs": [], "source": [ - "# job_name = returned_job.name\n", - "# Example if providing an specific Job name/ID\n", - "job_name = \"yellow_camera_1n84g0vcwp\"\n", + "# job_name = returned_job.name # If training job is in the same notebook\n", + "job_name = \"yellow_camera_1n84g0vcwp\" ## Example of providing an specific Job name/ID\n", "\n", "# Get the parent run\n", "mlflow_parent_run = mlflow_client.get_run(job_name)\n", "\n", - "print(\"Parent Run: \")\n", - "print(mlflow_parent_run)" + "# print(\"Parent Run: \")\n", + "# print(mlflow_parent_run)" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -241,7 +173,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -255,7 +187,7 @@ ")\n", "from azure.ai.ml.constants import BatchDeploymentOutputAction\n", "\n", - "model_name = \"test-gap-batch-endpoint\"\n", + "model_name = \"test-batch-endpoint\"\n", "batch_endpoint_name = \"gap-batch-\" + datetime.datetime.now().strftime(\n", " \"%m%d%H%M%f\"\n", ")\n", @@ -283,7 +215,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -310,16 +242,16 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'gap-batch-10241739762384'" + "'gap-batch-10282041765753'" ] }, - "execution_count": 40, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -330,13 +262,13 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ - "output_file = \"forecast.csv\"\n", + "output_file = \"forecast.csv\" # Where the predictions would be stored\n", "batch_deployment = BatchDeployment(\n", - " name=\"oj-non-mlflow-deployment\",\n", + " name=\"non-mlflow-deployment\",\n", " description=\"this is a sample non-mlflow deployment\",\n", " endpoint_name=batch_endpoint_name,\n", " model=registered_model,\n", @@ -361,82 +293,287 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32mUploading forecasting_script (0.0 MBs): 100%|##########| 2456/2456 [00:01<00:00, 1679.61it/s]\n", + "\u001b[39m\n", + "\n" + ] + } + ], "source": [ "ml_client.begin_create_or_update(batch_deployment).wait()" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gap-batch-10241739762384'" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "batch_endpoint_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data visualization of the train and test data" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ - "from azure.ai.ml import Input\n", - "from azure.ai.ml.constants import AssetTypes\n", - "my_test_data_input = Input(\n", - " type=AssetTypes.URI_FOLDER,\n", - " path=\"./data/testing-mltable-folder\",\n", - ")" + "df_train = pd.read_parquet('./data/training-mltable-folder/df_train.parquet') # We stored the training and test data during training\n", + "df_test = pd.read_parquet('./data/testing-mltable-folder/df_test.parquet')" ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 17, "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "

" + ], "text/plain": [ - "MLClient(credential=,\n", - " subscription_id=72c03bf3-4e69-41af-9532-dfcdc3eefef4,\n", - " resource_group_name=aml-benchmarking,\n", - " workspace_name=aml-benchmarking-rd)" + " date ext_predictor time_series_id y data_type\n", + "58 2000-01-02 04:00:00 70 ts1 33.655695 Training\n", + "59 2000-01-02 05:00:00 71 ts1 34.589008 Training" ] }, - "execution_count": 45, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ml_client" + "df_train.tail(2)" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 19, "metadata": {}, "outputs": [ { "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", + "
dateext_predictortime_series_idydata_type
02000-01-02 06:00:0072ts030.880051Testing
12000-01-02 07:00:0073ts031.234464Testing
\n", + "
" + ], "text/plain": [ - "'gap-batch-10241739762384'" + " date ext_predictor time_series_id y data_type\n", + "0 2000-01-02 06:00:00 72 ts0 30.880051 Testing\n", + "1 2000-01-02 07:00:00 73 ts0 31.234464 Testing" ] }, - "execution_count": 46, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "batch_endpoint_name" + "df_test.head(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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d+67774rXnuTV0aOHElKSgpz585l/vz51K5dmxUrVvDKK6/cdJ99+/bFbDbz1ltv8dJLL1GtWjWWLVvGqFGjbjlv5cqV2bZtG+PGjWP27NnExMQQGBhIrVq1GD16tPW4wYMHs2zZMlavXk1GRgalSpVi4sSJvPTSS9ZjOnXqxKBBg5g3bx5fffUVhmFcs0g6deoUTz75JJAzOhUSEkKjRo2YMWMGrVu3znVshQoVWLRoEa+99hrDhg0jODiYZ599liJFivD000/nOnb06NH89ddfvPnmmyQlJdG8eXNatmx5W94fEZGCzGTciV8hioiIiIiIFBC6JklEREREROQiKpJEREREREQuoiJJRERERETkIiqSRERERERELqIiSURERERE5CIqkkRERERERC5S6O+TZLFYOH36NJ6enphMJlvHERERERERGzEMg6SkJIoWLYqd3ZXHiwp9kXT69GlKlChh6xgiIiIiIpJPnDx5kuLFi19xf6Evkjw9PYGcF8LLy8vGaURERERExFYSExMpUaKEtUa4kkJfJF2YYufl5aUiSURERERErnkZjhZuEBERERERuYiKJBERERERkYuoSBIREREREblIob8m6XqZzWaysrJsHUOkUHB0dMTe3t7WMURERERuyl1fJBmGQXR0NPHx8baOIlKo+Pj4EBwcrPuTiYiISIFz1xdJFwqkwMBA3Nzc9IFO5BYZhkFqaipnz54FICQkxMaJRERERG7MXV0kmc1ma4Hk7+9v6zgihYarqysAZ8+eJTAwUFPvREREpEC5qxduuHANkpubm42TiBQ+F36udK2fiIiIFDR3dZF0gabYieQ9/VyJiIhIQaUiSURERERE5CIqkiRfOn78OCaTiZ07d950Hy1atGDIkCF5lik/GDt2LDVr1rR1DBEREZFCTUXSFfTq1QuTyYTJZMLR0ZGgoCBat27N//73PywWyw31NXv2bHx8fG4pz4Wi4Wpfs2fPvqVzFBTX+1osXryYCRMm3PF8ZrOZyZMnU7FiRVxdXfHz86NBgwZ8+umnt9z3sGHDWLduXR6kvLz/FmFjx461vqYODg4EBATQrFkz3n33XTIyMm5bDhERERFbuqtXt7uWNm3aMGvWLMxmM2fOnGHVqlU8//zzLFq0iGXLluHgcOdevhIlShAVFWXdnjJlCqtWrWLt2rXWNm9v7zuWx5au97W4sMLanTZu3Dg++ugjpk+fTt26dUlMTGTbtm3ExcXddJ+GYWA2m/Hw8MDDwyMP015blSpVWLt2LRaLhZiYGCIjI5k4cSJffvklkZGReHp63tE8IiIiIrebRpKuwtnZmeDgYIoVK0bt2rUZOXIk3377Ld9//32uUZu3336batWq4e7uTokSJXjuuedITk4GIDIykqeeeoqEhATrb+THjh0LwJdffkndunXx9PQkODiYxx9/3Hpvmf+yt7cnODjY+uXh4YGDgwPBwcGkp6dTtGhR9u7dm+sx7777LqVKlcJisRAZGYnJZGLFihVUr14dFxcXGjZsyB9//JHrMZs2baJp06a4urpSokQJBg8eTEpKyhVfoyNHjvDwww8TFBSEh4cH9erVy1WsAISGhjJp0iSefvppPD09KVmyJB9//HGuY3777Tdq1aqFi4sLdevWZceOHVc859Veiwtfrq6ul0y3Cw0NZeLEifTo0QMPDw9KlSrFsmXLOHfuHA8//DAeHh5Ur16dbdu23dJrsmzZMp577jkeeeQRSpcuTY0aNejduzfDhg2zHmOxWIiIiKB06dK4urpSo0YNFi1aZN1/4f36/vvvqVOnDs7OzmzatOmy0+0+/fRTKlWqhIuLCxUrVuTDDz+07svMzGTgwIGEhITg4uJCqVKliIiIuGL2y7nw2hYtWpRq1aoxaNAgNmzYwB9//MEbb7xxQ32JiIiIFAQqkm5Qy5YtqVGjBosXL7a22dnZ8d5777F3714+//xz1q9fz8svvwxAWFgY7777Ll5eXkRFRREVFWX9sJyVlcWECRPYtWsXS5cu5fjx4/Tq1euGM4WGhtKqVStmzZqVq33WrFn06tULO7t/3+aXXnqJqVOnsnXrVooUKUL79u2tSzQfOXKENm3a0LlzZ3bv3s38+fPZtGkTAwcOvOK5k5OTefDBB1m3bh07duygTZs2tG/fnhMnTuQ6burUqdbi57nnnuPZZ5/l4MGD1j7atWtH5cqV2b59O2PHjs1VUOSld955h8aNG7Njxw7atm3Lk08+SY8ePejevTu///47ZcuWpUePHhiGcdOvSXBwMOvXr+fcuXNXPCYiIoIvvviCmTNnsnfvXl544QW6d+/Ohg0bch33yiuvMHnyZPbv30/16tUv6WfOnDmMHj2a119/nf379zNp0iRGjRrF559/DsB7773H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6YUbUlNTKSwsJCQkpNh4SEgIO3bsOO/P5Obmkpub63ydkZFxTTOKiIiIiEjp4tJXkv6MhIQEAgMDnb8iIiKsjiQiIiIiIiWIS5ekihUrYrfbSUlJKTaekpJCaGjoeX/m+eefJz093fnr0KFD1yOqiIiIiIiUEi5dkjw9PWnatCmJiYnOMYfDQWJiInFxcef9GS8vLwICAor9EhERERERuVwu/UwSwJAhQ+jVqxfNmjWjRYsWjB8/ntOnT9OnTx+ro4mIiIiISCnk8iXp3nvv5fjx47zyyiskJyfTuHFj5syZ84fFHERERERERK4GmzHGWB3iWsrIyCAwMJD09HTdeiciIiIiUoZdbjdw6WeSRERERERErjeVJBERERERkSJUkkRERERERIpQSRIRERERESlCJUlERERERKQIlSQREREREZEiVJJERERERESKUEkSEREREREpQiVJRERERESkCHerA1xrxhjg7LfrioiIiIhI2XWuE5zrCBdS6kvSiRMnAIiIiLA4iYiIiIiIuIITJ04QGBh4wflSX5KCg4MBOHjw4EX/h5CypXnz5qxZs8bqGOJCdExIUToepKiMjAwiIiI4dOgQAQEBVscRF6G/J0qm9PR0IiMjnR3hQkp9SXJzO/vYVWBgoP5iEye73a7jQYrRMSFF6XiQ8wkICNBxIU76e6JkO9cRLjh/nXKIuJQBAwZYHUFcjI4JKUrHg4hciv6eKN1s5lJPLZVwGRkZBAYGkp6errYvIiIiV0znEiKlx+X+eS71V5K8vLwYPnw4Xl5eVkcRERGREkjnEiKlx+X+eS71V5JERERERESuRKm/kiQiIiIiInIlVJJERERERESKUEmSEuf9998nKioKb29vWrZsyerVq4vNr1ixgvbt2+Pn50dAQACtW7cmJyfnovtctGgRsbGxeHl5UatWLaZMmXLF7yvX35IlS+jatSthYWHYbDamT5/unMvPz2fYsGHccMMN+Pn5ERYWRs+ePTl69Ogl96vjoeS62DEBkJWVxcCBAwkPD8fHx4f69eszceLES+5306ZN3HLLLXh7exMREcHo0aP/sM23335L3bp18fb25oYbbmD27NlX62OJyFWmcwm5JCNSgnz11VfG09PTTJo0yWzdutX07dvXlC9f3qSkpBhjjFm+fLkJCAgwCQkJZsuWLWbHjh3m66+/NmfOnLng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W5KMObtU4sCBA52vCwsLTdWqVU1CQoJJS0szHh4e5rvvvnPO79ixwwBmxYoVF9znPffcY7p06VJsLC4uzrlMq8PhMKGhoWbMmDHO+fT0dOPl5WX+85//XK2PJn/BhAkTTGRkpPH09DQtWrQwK1euLDafkJBgwsPDja+vr4mLizO//PJLsfk2bdqYXr16FRtbuHChady4sfH09DQ1atQwkydPvuL3letv4cKFBvjDr169epl9+/addw4wCxcudO5Dx0PpcrFjwhhjkpKSTO/evU1YWJjx9vY2derUMWPHjjUOh8O5j169epk2bdoU2++vv/5qbr75ZuPl5WWqVq1q3nzzzT+89zfffGNq165tPD09TYMGDf6wJLRYQ+cScj46l5BLcemSdKnG3a9fPxMZGWkWLFhg1q5da+Li4kxcXFyxfbRv395MmDDB+fpy/rXvzTffNOXLlzczZswwmzZtMnfeeaepXr26ycnJuT4fXERERK4KnUuIyJ/h0iXJmIs37nNfABcUFGR8fX1N9+7dTVJSUrGfr1atmhk+fHixsUv9a9+5L4ALCQkxXl5e5tZbbzU7d+68Zp9RRERErh2dS4jIlbIZY8z1ewJKRERERETEtZX61e1ERERERESuhEqSiIiIiIhIESpJIiIiIiIiRagkiYiIiIiIFKGSJCIiIiIiUoRLlqT333+fqKgovL29admyJatXr3bOffzxx7Rt25aAgABsNhtpaWmXtc8pU6ZQvnz5axNYREREXM6FzidOnjzJE088QZ06dfDx8SEyMpJBgwaRnp5+yX2OGDGCxo0bX+PkImI1lytJX3/9NUOGDGH48OGsX7+emJgY4uPjOXbsGADZ2dl06tSJF154weKkIiIi4qoudj5x9OhRjh49ypgxY9iyZQtTpkxhzpw5PPLII1bHFhEX4XIlady4cfTt25c+ffpQv359Jk6ciK+vL5MmTQJg8ODBPPfcc9x4441/6X327NnDnXfeSUhICP7+/jRv3pz58+cX2yYqKopRo0bx8MMPU65cOSIjI/n444//0vuKiIjItXex84mGDRvy3//+l65du1KzZk3at2/PG2+8wcyZMykoKLii91mzZg233XYbFStWJDAwkDZt2rB+/fpi29hsNv7973/TvXt3fH19iY6O5ocffriaH1dErjKXKkl5eXmsW7eODh06OMfc3Nzo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AMuXLwfOLgHeuHFjwsLCGD16NMnJyfTo0YN//vOfjBo1Cji7BHjDhg0ZMGAADz/8MAsWLGDQoEH8+OOPxMfHX+dPLSIiIiIiVnH5kmSz2c47PnnyZHr37g2c/TLZp59+mv/85z/k5uYSHx/PBx984LyVDuDAgQM8/vjjLFq0CD8/P3r16sWbb76Ju/v/HstatGgRTz31FNu2bSM8PJyXX37Z+R4iIiIiIlI2uHxJEhERERERuZ5c/pkkERERERGR60klSUREREREpAiVJBERERERkSJUkkRERERERIpQSRIRERERESlCJUlERERERKQIlSQREREREZEiVJJERERERESKUEkSEREREREpQiVJRERKjN69e2Oz2bDZbHh4eBASEsJtt93GpEmTcDgcl72fKVOmUL58+WsXVERESjSVJBERKVE6depEUlIS+/fv56effqJdu3Y8+eSTdOnShYKCAqvjiYhIKaCSJCIiJYqXlxehoaFUrVqV2NhYXnjhBWbMmMFPP/3ElClTABg3bhw33HADfn5+RERE0L9/f7KysgBYtGgRffr0IT093XlVasSIEQDk5ubyzDPPULVqVfz8/GjZsiWLFi2y5oOKiIhlVJJERKTEa9++PTExMXz//fcAuLm58e6777J161Y+/fRTFixYwLPPPgtAq1atGD9+PAEBASQlJZGUlMQzzzwDwMCBA1mxYgVfffUVmzZt4h//+AedOnVi165dln02ERG5/mzGGGN1CBERkcvRu3dv0tLSmD59+h/m7rvvPjZt2sS2bdv+MPfdd9/Rr18/UlNTgbPPJA0ePJi0tDTnNgcPHqRGjRocPHiQsLAw53iHDh1o0aIFo0aNuuqfR0REXJO71QFERESuBmMMNpsNgPnz55OQkMCOHTvIyMigoKCAM2fOkJ2dja+v73l/fvPmzRQWFlK7du1i47m5uVSoUOGa5xcREdehkiQiIqXC9u3bqV69Ovv376dLly48/vjjvPHGGwQHB7N06VIeeeQR8vLyLliSsrKysNvtrFu3DrvdXmzO39//enwEERFxESpJIiJS4i1YsIDNmzfz1FNPsW7dOhwOB2PHjsXN7eyjt998802x7T09PSksLCw21qRJEwoLCzl27Bi33HLLdcsuIiKuRyVJRERKlNzcXJKTkyksLCQlJYU5c+aQkJBAly5d6NmzJ1u2bCE/P58JEybQtWtXli1bxsSJE4vtIyoqiqysLBITE4mJicHX15fatWvz4IMP0rNnT8aOHUuTJk04fvw4iYmJNGrUiM6dO1v0iUVE5HrT6nYiIlKizJkzhypVqhAVFUWnTp1YuHAh7777LjNmzMButxMTE8O4ceP417/+RcOGDfniiy9ISEgoto9WrVrRr18/7r33XipVqsTo0aMBmDx5Mj179uTpp5+mTp06dOvWjTVr1hAZGWnFRxUREYtodTsREREREZEidCVJRERERESkCJUkERERERGRIlSSREREREREilBJEhERERERKUIlSUREREREpAiVJBERERERkSJUkkRERERERIpQSRIRERERESlCJUlERERERKQIlSQREREREZEiVJJERERERESKUEkSEREREREp4v8DzwLyaN8pSAsAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "# Concatenate the training and testing DataFrames\n", + "df_plot = pd.concat([df_train, df_test])\n", + "\n", + "# Create a figure and axis\n", + "plt.figure(figsize=(10, 6))\n", + "ax = plt.gca() # Get current axis\n", + "\n", + "# Group by both 'data_type' and 'time_series_id'\n", + "for (data_type, time_series_id), df in df_plot.groupby(['data_type', TIME_SERIES_ID_COLUMN_NAME]):\n", + " df.plot(x='date', y=TARGET_COLUMN_NAME, label=f\"{data_type} - {time_series_id}\", ax=ax, legend=False)\n", + "\n", + "# Customize the plot\n", + "plt.xlabel('Date')\n", + "plt.ylabel('Value')\n", + "plt.title('Train and Test Data')\n", + "\n", + "# Manually create the legend after plotting\n", + "plt.legend(title=\"Data Type and Time Series ID\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting from the trained model\n", + "\n", + "In this section we will review the forecast interface for two main scenarios: forecasting right after the training data, and the more complex interface for forecasting when there is a gap (in the time sense) between training and testing data.\n", + "\n", + "## X_train is directly followed by the X_test\n", + "Let's first consider the case when the prediction period immediately follows the training data. This is typical in scenarios where we have the time to retrain the model every time we wish to forecast. Forecasts that are made on daily and slower cadence typically fall into this category. Retraining the model every time benefits the accuracy because the most recent data is often the most informative.\n", + "\n", + "\n", + "\"Description\"\n", + "\n", + "We use X_test as a forecast request to generate the predictions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get the test data for which we need the prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml import Input\n", + "from azure.ai.ml.constants import AssetTypes\n", + "my_test_data_input = Input(\n", + " type=AssetTypes.URI_FOLDER,\n", + " path=\"./data/test_data_scenarios\",\n", + ")" ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'type': 'uri_folder', 'path': './data/testing-mltable-folder'}" + "{'type': 'uri_folder', 'path': './data/test_data_scenarios'}" ] }, - "execution_count": 47, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -445,22 +582,44 @@ "my_test_data_input" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Invoke the endpoint with the test data" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [], + "source": [ + "# import os\n", + "# os.system(\"az login\")\n", + "\n", + "# def refresh_token():\n", + "# os.system(\"az account get-access-token\")\n", + "\n", + "# Call refresh_token() at regular intervals in your notebook, if necessary\n" + ] + }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "job = ml_client.batch_endpoints.invoke(\n", " endpoint_name=batch_endpoint_name,\n", - " input=my_test_data_input,\n", - " deployment_name=\"oj-non-mlflow-deployment\", # name is required as default deployment is not set\n", + " input=my_test_data_input, #Test data input\n", + " deployment_name=\"non-mlflow-deployment\", # name is required as default deployment is not set\n", ")" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -468,45 +627,480 @@ "output_type": "stream", "text": [ "Running\n", - "RunId: batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be\n", - "Web View: https://ml.azure.com/runs/batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", + "RunId: batchjob-45f67a27-5f29-4582-8388-be8d180658dd\n", + "Web View: https://ml.azure.com/runs/batchjob-45f67a27-5f29-4582-8388-be8d180658dd?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", "\n", "Streaming logs/azureml/executionlogs.txt\n", "========================================\n", "\n", - "[2024-10-24 12:12:27Z] Submitting 1 runs, first five are: 16057eb9:658e18fb-67ae-4a36-bbdf-fd8db589f027\n", - "[2024-10-24 12:20:40Z] Execution of experiment failed, update experiment status and cancel running nodes.\n", + "[2024-10-28 15:27:46Z] Submitting 1 runs, first five are: 7805450f:d9e63b6f-d1a4-4419-b3ac-87f8e334da18\n", + "[2024-10-28 15:34:03Z] Completing processing run id d9e63b6f-d1a4-4419-b3ac-87f8e334da18.\n", "\n", "Execution Summary\n", "=================\n", - "RunId: batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be\n", - "Web View: https://ml.azure.com/runs/batchjob-f47ea9fe-132f-4b91-b038-ca19fb71c9be?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n" + "RunId: batchjob-45f67a27-5f29-4582-8388-be8d180658dd\n", + "Web View: https://ml.azure.com/runs/batchjob-45f67a27-5f29-4582-8388-be8d180658dd?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", + "\n" + ] + } + ], + "source": [ + "job_name = job.name\n", + "batch_job = ml_client.jobs.get(name=job_name)\n", + "print(batch_job.status)\n", + "# stream the job logs\n", + "ml_client.jobs.stream(name=job_name)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('batchjob-45f67a27-5f29-4582-8388-be8d180658dd', ' ', 'forecast.csv')" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job_name,\" \", output_file" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Downloading artifact azureml://datastores/workspaceblobstore/paths/azureml/d9e63b6f-d1a4-4419-b3ac-87f8e334da18/score/ to outputs\n" + ] + } + ], + "source": [ + "# Get the predictions\n", + "download_path = \"./outputs/\"\n", + "ml_client.jobs.download(job_name, download_path=download_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "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", + "
dateext_predictortime_series_iddata_typeyprediction_intervalpredicted
02000-01-02 06:00:0072ts0Testing30.880051[30.07023297694423, 30.938095002841163]30.504164
12000-01-02 07:00:0073ts0Testing31.234464[30.948404564288648, 32.04935617279979]31.498880
22000-01-02 08:00:0074ts0Testing32.324927[32.30731438581761, 32.679879108573864]32.493597
32000-01-02 09:00:0075ts0Testing33.290239[33.18813407978154, 33.78849217191297]33.488313
42000-01-02 10:00:0076ts0Testing34.696732[34.06278818141216, 34.90327082758539]34.483030
\n", + "
" + ], + "text/plain": [ + " date ext_predictor time_series_id data_type y \\\n", + "0 2000-01-02 06:00:00 72 ts0 Testing 30.880051 \n", + "1 2000-01-02 07:00:00 73 ts0 Testing 31.234464 \n", + "2 2000-01-02 08:00:00 74 ts0 Testing 32.324927 \n", + "3 2000-01-02 09:00:00 75 ts0 Testing 33.290239 \n", + "4 2000-01-02 10:00:00 76 ts0 Testing 34.696732 \n", + "\n", + " prediction_interval predicted \n", + "0 [30.07023297694423, 30.938095002841163] 30.504164 \n", + "1 [30.948404564288648, 32.04935617279979] 31.498880 \n", + "2 [32.30731438581761, 32.679879108573864] 32.493597 \n", + "3 [33.18813407978154, 33.78849217191297] 33.488313 \n", + "4 [34.06278818141216, 34.90327082758539] 34.483030 " + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fcst_df = pd.read_csv(download_path + output_file, parse_dates=[TIME_COLUMN_NAME])\n", + "fcst_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting away from training data\n", + "Suppose we trained a model, some time passed, and now we want to apply the model without re-training. If the model \"looks back\" -- uses previous values of the target -- then we somehow need to provide those values to the model.\n", + "\n", + "\"Description\"\n", + "\n", + "The notion of forecast origin comes into play: **the forecast origin is the last period for which we have seen the target value.** This applies per time-series, so each time-series can have a different forecast origin.\n", + "\n", + "The part of data before the forecast origin is the **prediction context**. To provide the context values the model needs when it looks back, we pass definite values in y_test (aligned with corresponding times in X_test)." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "End of the data we trained on:\n", + "time_series_id\n", + "ts0 2000-01-02 05:00:00\n", + "ts1 2000-01-02 05:00:00\n", + "Name: date, dtype: datetime64[ns]\n", + "\n", + "Start of the data we want to predict on:\n", + "time_series_id\n", + "ts0 2000-01-02 18:00:00\n", + "ts1 2000-01-02 18:00:00\n", + "Name: date, dtype: datetime64[ns]\n" ] }, { - "ename": "JobException", - "evalue": "Exception : \n {\n \"error\": {\n \"code\": \"UserError\",\n \"message\": \"Pipeline has failed child jobs. For more details and logs, please go to the job detail page and check the child jobs.\",\n \"message_format\": \"Pipeline has failed child jobs. {0}\",\n \"message_parameters\": {},\n \"reference_code\": \"PipelineHasStepJobFailed\",\n \"details\": []\n },\n \"environment\": \"eastus\",\n \"location\": \"eastus\",\n \"time\": \"2024-10-24T12:20:40.0429Z\",\n \"component_name\": \"\"\n} ", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mJobException\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[49], line 5\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28mprint\u001b[39m(batch_job\u001b[38;5;241m.\u001b[39mstatus)\n\u001b[0;32m 4\u001b[0m \u001b[38;5;66;03m# stream the job logs\u001b[39;00m\n\u001b[1;32m----> 5\u001b[0m \u001b[43mml_client\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mjobs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mjob_name\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\core\\tracing\\decorator.py:94\u001b[0m, in \u001b[0;36mdistributed_trace..decorator..wrapper_use_tracer\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 92\u001b[0m span_impl_type \u001b[38;5;241m=\u001b[39m settings\u001b[38;5;241m.\u001b[39mtracing_implementation()\n\u001b[0;32m 93\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m span_impl_type \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m---> 94\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m func(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 96\u001b[0m \u001b[38;5;66;03m# Merge span is parameter is set, but only if no explicit parent are passed\u001b[39;00m\n\u001b[0;32m 97\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m merge_span \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m passed_in_parent:\n", - "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\ai\\ml\\_telemetry\\activity.py:289\u001b[0m, in \u001b[0;36mmonitor_with_activity..monitor..wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 285\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m tracer\u001b[38;5;241m.\u001b[39mspan():\n\u001b[0;32m 286\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m log_activity(\n\u001b[0;32m 287\u001b[0m logger\u001b[38;5;241m.\u001b[39mpackage_logger, activity_name \u001b[38;5;129;01mor\u001b[39;00m f\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m, activity_type, custom_dimensions\n\u001b[0;32m 288\u001b[0m ):\n\u001b[1;32m--> 289\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m f(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 290\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(logger, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpackage_logger\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m 291\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m log_activity(logger\u001b[38;5;241m.\u001b[39mpackage_logger, activity_name \u001b[38;5;129;01mor\u001b[39;00m f\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m, activity_type, custom_dimensions):\n", - "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\ai\\ml\\operations\\_job_operations.py:818\u001b[0m, in \u001b[0;36mJobOperations.stream\u001b[1;34m(self, name)\u001b[0m\n\u001b[0;32m 815\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _is_pipeline_child_job(job_object):\n\u001b[0;32m 816\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m PipelineChildJobError(job_id\u001b[38;5;241m=\u001b[39mjob_object\u001b[38;5;241m.\u001b[39mid)\n\u001b[1;32m--> 818\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_stream_logs_until_completion\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 819\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_runs_operations\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjob_object\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_datastore_operations\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrequests_pipeline\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_requests_pipeline\u001b[49m\n\u001b[0;32m 820\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azure\\ai\\ml\\operations\\_job_ops_helper.py:334\u001b[0m, in \u001b[0;36mstream_logs_until_completion\u001b[1;34m(run_operations, job_resource, datastore_operations, raise_exception_on_failed_job, requests_pipeline)\u001b[0m\n\u001b[0;32m 332\u001b[0m file_handle\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 333\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m--> 334\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m JobException(\n\u001b[0;32m 335\u001b[0m message\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mException : \u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(json\u001b[38;5;241m.\u001b[39mdumps(error, indent\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m4\u001b[39m)),\n\u001b[0;32m 336\u001b[0m target\u001b[38;5;241m=\u001b[39mErrorTarget\u001b[38;5;241m.\u001b[39mJOB,\n\u001b[0;32m 337\u001b[0m no_personal_data_message\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mException raised on failed job.\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 338\u001b[0m error_category\u001b[38;5;241m=\u001b[39mErrorCategory\u001b[38;5;241m.\u001b[39mSYSTEM_ERROR,\n\u001b[0;32m 339\u001b[0m )\n\u001b[0;32m 341\u001b[0m file_handle\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 342\u001b[0m file_handle\u001b[38;5;241m.\u001b[39mflush()\n", - "\u001b[1;31mJobException\u001b[0m: Exception : \n {\n \"error\": {\n \"code\": \"UserError\",\n \"message\": \"Pipeline has failed child jobs. For more details and logs, please go to the job detail page and check the child jobs.\",\n \"message_format\": \"Pipeline has failed child jobs. {0}\",\n \"message_parameters\": {},\n \"reference_code\": \"PipelineHasStepJobFailed\",\n \"details\": []\n },\n \"environment\": \"eastus\",\n \"location\": \"eastus\",\n \"time\": \"2024-10-24T12:20:40.0429Z\",\n \"component_name\": \"\"\n} " + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\sagoswami\\projects\\2024\\forecast_notebooks\\azureml-examples\\sdk\\python\\jobs\\automl-standalone-jobs\\automl-forecasting-forecast-function\\helper.py:39: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.\n", + " time_column_name: pd.date_range(\n" ] } ], "source": [ - "job_name = job.name\n", + "# Generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", + "from helper import get_timeseries, make_forecasting_query\n", + "X_context, y_context, X_away, y_away = get_timeseries(\n", + " train_len=42, # train data was 30 steps long\n", + " test_len=4,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=2,\n", + ")\n", + "\n", + "print(\"End of the data we trained on:\")\n", + "print(df_train.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())\n", + "\n", + "print(\"\\nStart of the data we want to predict on:\")\n", + "print(X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a gap of 12 hours between end of training and beginning of X_away. (It looks like 13 because all timestamps point to the start of the one hour periods.) Using only X_away will fail without adding context data for the model to consume" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "x_gap_test = X_away.copy()\n", + "x_gap_test[\"y\"] = y_away\n", + "x_gap_test['data_type'] = 'test' # Dummy data\n", + "\n", + "x_gap_test.to_csv(\"./data/test_gap_scenario/gap_test_data.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "my_test_data_gap_input = Input(\n", + " type=AssetTypes.URI_FOLDER,\n", + " path=\"./data/test_gap_scenario/\", # Path to the data folder that has the test data with gap\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [], + "source": [ + "gap_job = ml_client.batch_endpoints.invoke(\n", + " endpoint_name=batch_endpoint_name,\n", + " input=my_test_data_gap_input, #Test data input\n", + " deployment_name=\"non-mlflow-deployment\", # name is required as default deployment is not set\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running\n", + "RunId: batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65\n", + "Web View: https://ml.azure.com/runs/batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", + "\n", + "Streaming logs/azureml/executionlogs.txt\n", + "========================================\n", + "\n", + "[2024-10-28 18:28:54Z] Submitting 1 runs, first five are: 7805450f:418b0b81-c680-48f7-be2c-22e8add701f6\n", + "[2024-10-28 18:37:22Z] Completing processing run id 418b0b81-c680-48f7-be2c-22e8add701f6.\n", + "\n", + "Execution Summary\n", + "=================\n", + "RunId: batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65\n", + "Web View: https://ml.azure.com/runs/batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", + "\n" + ] + } + ], + "source": [ + "job_name = gap_job.name\n", "batch_job = ml_client.jobs.get(name=job_name)\n", "print(batch_job.status)\n", "# stream the job logs\n", "ml_client.jobs.stream(name=job_name)" ] }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Downloading artifact azureml://datastores/workspaceblobstore/paths/azureml/418b0b81-c680-48f7-be2c-22e8add701f6/score/ to outputs\\gap_scenario\n" + ] + } + ], + "source": [ + "# Get the predictions\n", + "gap_download_path = \"./outputs/gap_scenario/\"\n", + "ml_client.jobs.download(job_name, download_path=gap_download_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "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", + "
Unnamed: 0dateext_predictortime_series_iddata_typeyprediction_intervalpredicted
0422000-01-02 18:00:0084ts0test42.253241[42.00682831806445, 42.87469034396139]42.440759
1432000-01-02 19:00:0085ts0test43.126377[42.884999905408854, 43.98595151391999]43.435476
2442000-01-02 20:00:0086ts0test44.532234[44.2439097269378, 44.61647444969405]44.430192
3452000-01-02 21:00:0087ts0test45.489004[45.124729420901716, 45.725087513033145]45.424908
4422000-01-02 18:00:0084ts1test47.127495[37.27487135377912, 38.14273337967606]37.708802
\n", + "
" + ], + "text/plain": [ + " Unnamed: 0 date ext_predictor time_series_id data_type \\\n", + "0 42 2000-01-02 18:00:00 84 ts0 test \n", + "1 43 2000-01-02 19:00:00 85 ts0 test \n", + "2 44 2000-01-02 20:00:00 86 ts0 test \n", + "3 45 2000-01-02 21:00:00 87 ts0 test \n", + "4 42 2000-01-02 18:00:00 84 ts1 test \n", + "\n", + " y prediction_interval predicted \n", + "0 42.253241 [42.00682831806445, 42.87469034396139] 42.440759 \n", + "1 43.126377 [42.884999905408854, 43.98595151391999] 43.435476 \n", + "2 44.532234 [44.2439097269378, 44.61647444969405] 44.430192 \n", + "3 45.489004 [45.124729420901716, 45.725087513033145] 45.424908 \n", + "4 47.127495 [37.27487135377912, 38.14273337967606] 37.708802 " + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gap_fcst_df = pd.read_csv(gap_download_path + output_file, parse_dates=[TIME_COLUMN_NAME])\n", + "gap_fcst_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [], + "source": [ + "gap_fcst_df['data_type']=\"gap_forecast\"" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -514,6 +1108,28 @@ "# Local inferencing from model pickle\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. **Model** \n", + " We will need the MLflow model, which is downloaded at the end of the training notebook. Follow any training notebook to get the model. The MLflow model is usually downloaded to the folder: `./artifact_downloads/outputs/mlflow-model`.\n", + "\n", + "2. **Environment** \n", + " We will need the environment to load the model. Please run the following commands to create the environment (the conda file is usually downloaded to: `./artifact_downloads/outputs/mlflow-model/conda.yaml`):\n", + " - `conda env create --file `\n", + " - `conda activate project_environment`\n", + "\n", + "3. **Register environment as kernel** \n", + " - Please run the following command to register the environment as a kernel: \n", + " ```bash\n", + " python -m ipykernel install --user --name project_environment --display-name \"model-inference\"\n", + " ```\n", + " - Refresh the kernel and then select the newly created kernel named `model-inference` from the kernel dropdown.\n", + " \n", + " Now we are good to run this notebook in the newly created kernel." + ] + }, { "cell_type": "code", "execution_count": 3, @@ -870,13 +1486,6 @@ "X_show[X_show['time_series_id'] == \"ts1\"][[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Forecasting using batch endpoint" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py index 56135869a89..513be30b7d5 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/forecasting_script/forecasting_script.py @@ -29,10 +29,13 @@ def run(mini_batch): print(f"run method start: {__file__}, run({mini_batch})") resultList = [] for test in mini_batch: - if os.path.splitext(test)[-1] != ".csv": - continue + if os.path.splitext(test)[-1] == ".parquet": + X_test = pd.read_parquet(test) + elif os.path.splitext(test)[-1] == ".csv": + X_test = pd.read_csv(test, parse_dates=[fitted_model.time_column_name]) + else: + continue # Skip if it's neither a Parquet nor CSV file - X_test = pd.read_csv(test, parse_dates=[fitted_model.time_column_name]) y_test = X_test.pop(target_column_name).values # We have default quantiles values set as below(95th percentile) From c5ff12b69910344e0222737bcf8b442aa4ee834e Mon Sep 17 00:00:00 2001 From: Sampurna Goswami Date: Tue, 29 Oct 2024 01:04:40 +0530 Subject: [PATCH 3/6] Cleanup code and delete outputs --- ...casting-function-gap-batch-inference.ipynb | 720 +++--------------- ...casting-function-gap-local-inference.ipynb | 31 +- ...ml-forecasting-function-gap-training.ipynb | 22 +- .../helper.py | 3 +- 4 files changed, 129 insertions(+), 647 deletions(-) diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb index 7b15a894541..c855474e3e1 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb @@ -44,17 +44,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "azureml://eastus.api.azureml.ms/mlflow/v1.0/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourceGroups/aml-benchmarking/providers/Microsoft.MachineLearningServices/workspaces/aml-benchmarking-rd\n" - ] - } - ], + "outputs": [], "source": [ "import mlflow\n", "import mlflow.sklearn\n", @@ -62,6 +54,7 @@ "\n", "from azure.identity import DefaultAzureCredential\n", "from azure.ai.ml import MLClient\n", + "\n", "credential = DefaultAzureCredential()\n", "ml_client = None\n", "\n", @@ -81,26 +74,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Current tracking uri: azureml://eastus.api.azureml.ms/mlflow/v1.0/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourceGroups/aml-benchmarking/providers/Microsoft.MachineLearningServices/workspaces/aml-benchmarking-rd\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\azureml\\mlflow\\_protos\\aml_service_pb2.py:10: UserWarning: google.protobuf.service module is deprecated. RPC implementations should provide code generator plugins which generate code specific to the RPC implementation. service.py will be removed in Jan 2025\n", - " from google.protobuf import service as _service\n" - ] - } - ], + "outputs": [], "source": [ "from mlflow.tracking.client import MlflowClient\n", "from mlflow.artifacts import download_artifacts\n", @@ -120,7 +96,7 @@ "outputs": [], "source": [ "# job_name = returned_job.name # If training job is in the same notebook\n", - "job_name = \"yellow_camera_1n84g0vcwp\" ## Example of providing an specific Job name/ID\n", + "job_name = \"yellow_camera_1n84g0vcwp\" ## Example of providing an specific Job name/ID\n", "\n", "# Get the parent run\n", "mlflow_parent_run = mlflow_client.get_run(job_name)\n", @@ -131,35 +107,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Found best child run id: yellow_camera_1n84g0vcwp_4\n", - "Best child run: \n", - ", info=, inputs=>\n" - ] - } - ], + "outputs": [], "source": [ "# Get the best model's child run\n", "best_child_run_id = mlflow_parent_run.data.tags[\"automl_best_child_run_id\"]\n", @@ -188,9 +138,7 @@ "from azure.ai.ml.constants import BatchDeploymentOutputAction\n", "\n", "model_name = \"test-batch-endpoint\"\n", - "batch_endpoint_name = \"gap-batch-\" + datetime.datetime.now().strftime(\n", - " \"%m%d%H%M%f\"\n", - ")\n", + "batch_endpoint_name = \"gap-batch-\" + datetime.datetime.now().strftime(\"%m%d%H%M%f\")\n", "\n", "model = Model(\n", " path=f\"azureml://jobs/{best_run.info.run_id}/outputs/artifacts/outputs/model.pkl\",\n", @@ -237,25 +185,14 @@ " idle_time_before_scale_down=120,\n", " )\n", " poller = ml_client.begin_create_or_update(compute)\n", - " poller.wait()\n" + " poller.wait()" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'gap-batch-10282041765753'" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "batch_endpoint_name" ] @@ -266,7 +203,7 @@ "metadata": {}, "outputs": [], "source": [ - "output_file = \"forecast.csv\" # Where the predictions would be stored\n", + "output_file = \"forecast.csv\" # Where the predictions would be stored\n", "batch_deployment = BatchDeployment(\n", " name=\"non-mlflow-deployment\",\n", " description=\"this is a sample non-mlflow deployment\",\n", @@ -279,53 +216,32 @@ " \"TARGET_COLUMN_NAME\": TARGET_COLUMN_NAME,\n", " },\n", " compute=cluster_name,\n", - " instance_count=1, #2\n", - " max_concurrency_per_instance=1, #2\n", - " mini_batch_size=1, #10\n", + " instance_count=1, # 2\n", + " max_concurrency_per_instance=1, # 2\n", + " mini_batch_size=1, # 10\n", " output_action=BatchDeploymentOutputAction.APPEND_ROW,\n", " output_file_name=output_file,\n", " retry_settings=BatchRetrySettings(max_retries=3, timeout=30),\n", " logging_level=\"info\",\n", " properties={\"include_output_header\": \"true\"},\n", " tags={\"include_output_header\": \"true\"},\n", - ")\n" + ")" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32mUploading forecasting_script (0.0 MBs): 100%|##########| 2456/2456 [00:01<00:00, 1679.61it/s]\n", - "\u001b[39m\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "ml_client.begin_create_or_update(batch_deployment).wait()" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'gap-batch-10241739762384'" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "batch_endpoint_name" ] @@ -343,166 +259,38 @@ "metadata": {}, "outputs": [], "source": [ - "df_train = pd.read_parquet('./data/training-mltable-folder/df_train.parquet') # We stored the training and test data during training\n", - "df_test = pd.read_parquet('./data/testing-mltable-folder/df_test.parquet')" + "df_train = pd.read_parquet(\n", + " \"./data/training-mltable-folder/df_train.parquet\"\n", + ") # We stored the training and test data during training\n", + "df_test = pd.read_parquet(\"./data/testing-mltable-folder/df_test.parquet\")" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "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", - "
dateext_predictortime_series_idydata_type
582000-01-02 04:00:0070ts133.655695Training
592000-01-02 05:00:0071ts134.589008Training
\n", - "
" - ], - "text/plain": [ - " date ext_predictor time_series_id y data_type\n", - "58 2000-01-02 04:00:00 70 ts1 33.655695 Training\n", - "59 2000-01-02 05:00:00 71 ts1 34.589008 Training" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "df_train.tail(2)" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "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", - "
dateext_predictortime_series_idydata_type
02000-01-02 06:00:0072ts030.880051Testing
12000-01-02 07:00:0073ts031.234464Testing
\n", - "
" - ], - "text/plain": [ - " date ext_predictor time_series_id y data_type\n", - "0 2000-01-02 06:00:00 72 ts0 30.880051 Testing\n", - "1 2000-01-02 07:00:00 73 ts0 31.234464 Testing" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "df_test.head(2)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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d+67774rXnuTV0aOHElKSgpz585l/vz51K5dmxUrVvDKK6/cdJ99+/bFbDbz1ltv8dJLL1GtWjWWLVvGqFGjbjlv5cqV2bZtG+PGjWP27NnExMQQGBhIrVq1GD16tPW4wYMHs2zZMlavXk1GRgalSpVi4sSJvPTSS9ZjOnXqxKBBg5g3bx5fffUVhmFcs0g6deoUTz75JJAzOhUSEkKjRo2YMWMGrVu3znVshQoVWLRoEa+99hrDhg0jODiYZ599liJFivD000/nOnb06NH89ddfvPnmmyQlJdG8eXNatmx5W94fEZGCzGTciV8hioiIiIiIFBC6JklEREREROQiKpJEREREREQuoiJJRERERETkIiqSRERERERELqIiSURERERE5CIqkkRERERERC5S6O+TZLFYOH36NJ6enphMJlvHERERERERGzEMg6SkJIoWLYqd3ZXHiwp9kXT69GlKlChh6xgiIiIiIpJPnDx5kuLFi19xf6Evkjw9PYGcF8LLy8vGaURERERExFYSExMpUaKEtUa4kkJfJF2YYufl5aUiSURERERErnkZjhZuEBERERERuYiKJBERERERkYuoSBIREREREblIob8m6XqZzWaysrJsHUOkUHB0dMTe3t7WMURERERuyl1fJBmGQXR0NPHx8baOIlKo+Pj4EBwcrPuTiYiISIFz1xdJFwqkwMBA3Nzc9IFO5BYZhkFqaipnz54FICQkxMaJRERERG7MXV0kmc1ma4Hk7+9v6zgihYarqysAZ8+eJTAwUFPvREREpEC5qxduuHANkpubm42TiBQ+F36udK2fiIiIFDR3dZF0gabYieQ9/VyJiIhIQaUiSURERERE5CIqkiRfOn78OCaTiZ07d950Hy1atGDIkCF5lik/GDt2LDVr1rR1DBEREZFCTUXSFfTq1QuTyYTJZMLR0ZGgoCBat27N//73PywWyw31NXv2bHx8fG4pz4Wi4Wpfs2fPvqVzFBTX+1osXryYCRMm3PF8ZrOZyZMnU7FiRVxdXfHz86NBgwZ8+umnt9z3sGHDWLduXR6kvLz/FmFjx461vqYODg4EBATQrFkz3n33XTIyMm5bDhERERFbuqtXt7uWNm3aMGvWLMxmM2fOnGHVqlU8//zzLFq0iGXLluHgcOdevhIlShAVFWXdnjJlCqtWrWLt2rXWNm9v7zuWx5au97W4sMLanTZu3Dg++ugjpk+fTt26dUlMTGTbtm3ExcXddJ+GYWA2m/Hw8MDDwyMP015blSpVWLt2LRaLhZiYGCIjI5k4cSJffvklkZGReHp63tE8IiIiIrebRpKuwtnZmeDgYIoVK0bt2rUZOXIk3377Ld9//32uUZu3336batWq4e7uTokSJXjuuedITk4GIDIykqeeeoqEhATrb+THjh0LwJdffkndunXx9PQkODiYxx9/3Hpvmf+yt7cnODjY+uXh4YGDgwPBwcGkp6dTtGhR9u7dm+sx7777LqVKlcJisRAZGYnJZGLFihVUr14dFxcXGjZsyB9//JHrMZs2baJp06a4urpSokQJBg8eTEpKyhVfoyNHjvDwww8TFBSEh4cH9erVy1WsAISGhjJp0iSefvppPD09KVmyJB9//HGuY3777Tdq1aqFi4sLdevWZceOHVc859Veiwtfrq6ul0y3Cw0NZeLEifTo0QMPDw9KlSrFsmXLOHfuHA8//DAeHh5Ur16dbdu23dJrsmzZMp577jkeeeQRSpcuTY0aNejduzfDhg2zHmOxWIiIiKB06dK4urpSo0YNFi1aZN1/4f36/vvvqVOnDs7OzmzatOmy0+0+/fRTKlWqhIuLCxUrVuTDDz+07svMzGTgwIGEhITg4uJCqVKliIiIuGL2y7nw2hYtWpRq1aoxaNAgNmzYwB9//MEbb7xxQ32JiIiIFAQqkm5Qy5YtqVGjBosXL7a22dnZ8d5777F3714+//xz1q9fz8svvwxAWFgY7777Ll5eXkRFRREVFWX9sJyVlcWECRPYtWsXS5cu5fjx4/Tq1euGM4WGhtKqVStmzZqVq33WrFn06tULO7t/3+aXXnqJqVOnsnXrVooUKUL79u2tSzQfOXKENm3a0LlzZ3bv3s38+fPZtGkTAwcOvOK5k5OTefDBB1m3bh07duygTZs2tG/fnhMnTuQ6burUqdbi57nnnuPZZ5/l4MGD1j7atWtH5cqV2b59O2PHjs1VUOSld955h8aNG7Njxw7atm3Lk08+SY8ePejevTu///47ZcuWpUePHhiGcdOvSXBwMOvXr+fcuXNXPCYiIoIvvviCmTNnsnfvXl544QW6d+/Ohg0bch33yiuvMHnyZPbv30/16tUv6WfOnDmMHj2a119/nf379zNp0iRGjRrF559/DsB7773H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6YUbUlNTKSwsJCQkpNh4SEgIO3bsOO/P5Obmkpub63ydkZFxTTOKiIiIiEjp4tJXkv6MhIQEAgMDnb8iIiKsjiQiIiIiIiWIS5ekihUrYrfbSUlJKTaekpJCaGjoeX/m+eefJz093fnr0KFD1yOqiIiIiIiUEi5dkjw9PWnatCmJiYnOMYfDQWJiInFxcef9GS8vLwICAor9EhERERERuVwu/UwSwJAhQ+jVqxfNmjWjRYsWjB8/ntOnT9OnTx+ro4mIiIiISCnk8iXp3nvv5fjx47zyyiskJyfTuHFj5syZ84fFHERERERERK4GmzHGWB3iWsrIyCAwMJD09HTdeiciIiIiUoZdbjdw6WeSRERERERErjeVJBERERERkSJUkkRERERERIpQSRIRERERESlCJUlERERERKQIlSQREREREZEiVJJERERERESKUEkSEREREREpQiVJRERERESkCHerA1xrxhjg7LfrioiIiIhI2XWuE5zrCBdS6kvSiRMnAIiIiLA4iYiIiIiIuIITJ04QGBh4wflSX5KCg4MBOHjw4EX/h5CypXnz5qxZs8bqGOJCdExIUToepKiMjAwiIiI4dOgQAQEBVscRF6G/J0qm9PR0IiMjnR3hQkp9SXJzO/vYVWBgoP5iEye73a7jQYrRMSFF6XiQ8wkICNBxIU76e6JkO9cRLjh/nXKIuJQBAwZYHUFcjI4JKUrHg4hciv6eKN1s5lJPLZVwGRkZBAYGkp6errYvIiIiV0znEiKlx+X+eS71V5K8vLwYPnw4Xl5eVkcRERGREkjnEiKlx+X+eS71V5JERERERESuRKm/kiQiIiIiInIlVJJERERERESKUEmSEuf9998nKioKb29vWrZsyerVq4vNr1ixgvbt2+Pn50dAQACtW7cmJyfnovtctGgRsbGxeHl5UatWLaZMmXLF7yvX35IlS+jatSthYWHYbDamT5/unMvPz2fYsGHccMMN+Pn5ERYWRs+ePTl69Ogl96vjoeS62DEBkJWVxcCBAwkPD8fHx4f69eszceLES+5306ZN3HLLLXh7exMREcHo0aP/sM23335L3bp18fb25oYbbmD27NlX62OJyFWmcwm5JCNSgnz11VfG09PTTJo0yWzdutX07dvXlC9f3qSkpBhjjFm+fLkJCAgwCQkJZsuWLWbHjh3m66+/NmfOnLng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W5KMObtU4sCBA52vCwsLTdWqVU1CQoJJS0szHh4e5rvvvnPO79ixwwBmxYoVF9znPffcY7p06VJsLC4uzrlMq8PhMKGhoWbMmDHO+fT0dOPl5WX+85//XK2PJn/BhAkTTGRkpPH09DQtWrQwK1euLDafkJBgwsPDja+vr4mLizO//PJLsfk2bdqYXr16FRtbuHChady4sfH09DQ1atQwkydPvuL3letv4cKFBvjDr169epl9+/addw4wCxcudO5Dx0PpcrFjwhhjkpKSTO/evU1YWJjx9vY2derUMWPHjjUOh8O5j169epk2bdoU2++vv/5qbr75ZuPl5WWqVq1q3nzzzT+89zfffGNq165tPD09TYMGDf6wJLRYQ+cScj46l5BLcemSdKnG3a9fPxMZGWkWLFhg1q5da+Li4kxcXFyxfbRv395MmDDB+fpy/rXvzTffNOXLlzczZswwmzZtMnfeeaepXr26ycnJuT4fXERERK4KnUuIyJ/h0iXJmIs37nNfABcUFGR8fX1N9+7dTVJSUrGfr1atmhk+fHixsUv9a9+5L4ALCQkxXl5e5tZbbzU7d+68Zp9RRERErh2dS4jIlbIZY8z1ewJKRERERETEtZX61e1ERERERESuhEqSiIiIiIhIESpJIiIiIiIiRagkiYiIiIiIFKGSJCIiIiIiUoRLlqT333+fqKgovL29admyJatXr3bOffzxx7Rt25aAgABsNhtpaWmXtc8pU6ZQvnz5axNYREREXM6FzidOnjzJE088QZ06dfDx8SEyMpJBgwaRnp5+yX2OGDGCxo0bX+PkImI1lytJX3/9NUOGDGH48OGsX7+emJgY4uPjOXbsGADZ2dl06tSJF154weKkIiIi4qoudj5x9OhRjh49ypgxY9iyZQtTpkxhzpw5PPLII1bHFhEX4XIlady4cfTt25c+ffpQv359Jk6ciK+vL5MmTQJg8ODBPPfcc9x4441/6X327NnDnXfeSUhICP7+/jRv3pz58+cX2yYqKopRo0bx8MMPU65cOSIjI/n444//0vuKiIjItXex84mGDRvy3//+l65du1KzZk3at2/PG2+8wcyZMykoKLii91mzZg233XYbFStWJDAwkDZt2rB+/fpi29hsNv7973/TvXt3fH19iY6O5ocffriaH1dErjKXKkl5eXmsW7eODh06OMfc3Nzo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AMuXLwfOLgHeuHFjwsLCGD16NMnJyfTo0YN//vOfjBo1Cji7BHjDhg0ZMGAADz/8MAsWLGDQoEH8+OOPxMfHX+dPLSIiIiIiVnH5kmSz2c47PnnyZHr37g2c/TLZp59+mv/85z/k5uYSHx/PBx984LyVDuDAgQM8/vjjLFq0CD8/P3r16sWbb76Ju/v/HstatGgRTz31FNu2bSM8PJyXX37Z+R4iIiIiIlI2uHxJEhERERERuZ5c/pkkERERERGR60klSUREREREpAiVJBERERERkSJUkkRERERERIpQSRIRERERESlCJUlERERERKQIlSQREREREZEiVJJERERERESKUEkSEREREREpQiVJRERKjN69e2Oz2bDZbHh4eBASEsJtt93GpEmTcDgcl72fKVOmUL58+WsXVERESjSVJBERKVE6depEUlIS+/fv56effqJdu3Y8+eSTdOnShYKCAqvjiYhIKaCSJCIiJYqXlxehoaFUrVqV2NhYXnjhBWbMmMFPP/3ElClTABg3bhw33HADfn5+RERE0L9/f7KysgBYtGgRffr0IT093XlVasSIEQDk5ubyzDPPULVqVfz8/GjZsiWLFi2y5oOKiIhlVJJERKTEa9++PTExMXz//fcAuLm58e6777J161Y+/fRTFixYwLPPPgtAq1atGD9+PAEBASQlJZGUlMQzzzwDwMCBA1mxYgVfffUVmzZt4h//+AedOnVi165dln02ERG5/mzGGGN1CBERkcvRu3dv0tLSmD59+h/m7rvvPjZt2sS2bdv+MPfdd9/Rr18/UlNTgbPPJA0ePJi0tDTnNgcPHqRGjRocPHiQsLAw53iHDh1o0aIFo0aNuuqfR0REXJO71QFERESuBmMMNpsNgPnz55OQkMCOHTvIyMigoKCAM2fOkJ2dja+v73l/fvPmzRQWFlK7du1i47m5uVSoUOGa5xcREdehkiQiIqXC9u3bqV69Ovv376dLly48/vjjvPHGGwQHB7N06VIeeeQR8vLyLliSsrKysNvtrFu3DrvdXmzO39//enwEERFxESpJIiJS4i1YsIDNmzfz1FNPsW7dOhwOB2PHjsXN7eyjt998802x7T09PSksLCw21qRJEwoLCzl27Bi33HLLdcsuIiKuRyVJRERKlNzcXJKTkyksLCQlJYU5c+aQkJBAly5d6NmzJ1u2bCE/P58JEybQtWtXli1bxsSJE4vtIyoqiqysLBITE4mJicHX15fatWvz4IMP0rNnT8aOHUuTJk04fvw4iYmJNGrUiM6dO1v0iUVE5HrT6nYiIlKizJkzhypVqhAVFUWnTp1YuHAh7777LjNmzMButxMTE8O4ceP417/+RcOGDfniiy9ISEgoto9WrVrRr18/7r33XipVqsTo0aMBmDx5Mj179uTpp5+mTp06dOvWjTVr1hAZGWnFRxUREYtodTsREREREZEidCVJRERERESkCJUkERERERGRIlSSREREREREilBJEhERERERKUIlSUREREREpAiVJBERERERkSJUkkRERERERIpQSRIRERERESlCJUlERERERKQIlSQREREREZEiVJJERERERESKUEkSEREREREp4v8DzwLyaN8pSAsAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "\n", "# Concatenate the training and testing DataFrames\n", "df_plot = pd.concat([df_train, df_test])\n", "\n", @@ -511,13 +299,21 @@ "ax = plt.gca() # Get current axis\n", "\n", "# Group by both 'data_type' and 'time_series_id'\n", - "for (data_type, time_series_id), df in df_plot.groupby(['data_type', TIME_SERIES_ID_COLUMN_NAME]):\n", - " df.plot(x='date', y=TARGET_COLUMN_NAME, label=f\"{data_type} - {time_series_id}\", ax=ax, legend=False)\n", + "for (data_type, time_series_id), df in df_plot.groupby(\n", + " [\"data_type\", TIME_SERIES_ID_COLUMN_NAME]\n", + "):\n", + " df.plot(\n", + " x=\"date\",\n", + " y=TARGET_COLUMN_NAME,\n", + " label=f\"{data_type} - {time_series_id}\",\n", + " ax=ax,\n", + " legend=False,\n", + " )\n", "\n", "# Customize the plot\n", - "plt.xlabel('Date')\n", - "plt.ylabel('Value')\n", - "plt.title('Train and Test Data')\n", + "plt.xlabel(\"Date\")\n", + "plt.ylabel(\"Value\")\n", + "plt.title(\"Train and Test Data\")\n", "\n", "# Manually create the legend after plotting\n", "plt.legend(title=\"Data Type and Time Series ID\")\n", @@ -556,6 +352,7 @@ "source": [ "from azure.ai.ml import Input\n", "from azure.ai.ml.constants import AssetTypes\n", + "\n", "my_test_data_input = Input(\n", " type=AssetTypes.URI_FOLDER,\n", " path=\"./data/test_data_scenarios\",\n", @@ -564,20 +361,9 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'type': 'uri_folder', 'path': './data/test_data_scenarios'}" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "my_test_data_input" ] @@ -601,7 +387,7 @@ "# def refresh_token():\n", "# os.system(\"az account get-access-token\")\n", "\n", - "# Call refresh_token() at regular intervals in your notebook, if necessary\n" + "# Call refresh_token() at regular intervals in your notebook, if necessary" ] }, { @@ -612,38 +398,16 @@ "source": [ "job = ml_client.batch_endpoints.invoke(\n", " endpoint_name=batch_endpoint_name,\n", - " input=my_test_data_input, #Test data input\n", + " input=my_test_data_input, # Test data input\n", " deployment_name=\"non-mlflow-deployment\", # name is required as default deployment is not set\n", ")" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running\n", - "RunId: batchjob-45f67a27-5f29-4582-8388-be8d180658dd\n", - "Web View: https://ml.azure.com/runs/batchjob-45f67a27-5f29-4582-8388-be8d180658dd?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", - "\n", - "Streaming logs/azureml/executionlogs.txt\n", - "========================================\n", - "\n", - "[2024-10-28 15:27:46Z] Submitting 1 runs, first five are: 7805450f:d9e63b6f-d1a4-4419-b3ac-87f8e334da18\n", - "[2024-10-28 15:34:03Z] Completing processing run id d9e63b6f-d1a4-4419-b3ac-87f8e334da18.\n", - "\n", - "Execution Summary\n", - "=================\n", - "RunId: batchjob-45f67a27-5f29-4582-8388-be8d180658dd\n", - "Web View: https://ml.azure.com/runs/batchjob-45f67a27-5f29-4582-8388-be8d180658dd?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "job_name = job.name\n", "batch_job = ml_client.jobs.get(name=job_name)\n", @@ -654,37 +418,18 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('batchjob-45f67a27-5f29-4582-8388-be8d180658dd', ' ', 'forecast.csv')" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "job_name,\" \", output_file" + "job_name, \" \", output_file" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Downloading artifact azureml://datastores/workspaceblobstore/paths/azureml/d9e63b6f-d1a4-4419-b3ac-87f8e334da18/score/ to outputs\n" - ] - } - ], + "outputs": [], "source": [ "# Get the predictions\n", "download_path = \"./outputs/\"\n", @@ -693,115 +438,9 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "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", - "
dateext_predictortime_series_iddata_typeyprediction_intervalpredicted
02000-01-02 06:00:0072ts0Testing30.880051[30.07023297694423, 30.938095002841163]30.504164
12000-01-02 07:00:0073ts0Testing31.234464[30.948404564288648, 32.04935617279979]31.498880
22000-01-02 08:00:0074ts0Testing32.324927[32.30731438581761, 32.679879108573864]32.493597
32000-01-02 09:00:0075ts0Testing33.290239[33.18813407978154, 33.78849217191297]33.488313
42000-01-02 10:00:0076ts0Testing34.696732[34.06278818141216, 34.90327082758539]34.483030
\n", - "
" - ], - "text/plain": [ - " date ext_predictor time_series_id data_type y \\\n", - "0 2000-01-02 06:00:00 72 ts0 Testing 30.880051 \n", - "1 2000-01-02 07:00:00 73 ts0 Testing 31.234464 \n", - "2 2000-01-02 08:00:00 74 ts0 Testing 32.324927 \n", - "3 2000-01-02 09:00:00 75 ts0 Testing 33.290239 \n", - "4 2000-01-02 10:00:00 76 ts0 Testing 34.696732 \n", - "\n", - " prediction_interval predicted \n", - "0 [30.07023297694423, 30.938095002841163] 30.504164 \n", - "1 [30.948404564288648, 32.04935617279979] 31.498880 \n", - "2 [32.30731438581761, 32.679879108573864] 32.493597 \n", - "3 [33.18813407978154, 33.78849217191297] 33.488313 \n", - "4 [34.06278818141216, 34.90327082758539] 34.483030 " - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "fcst_df = pd.read_csv(download_path + output_file, parse_dates=[TIME_COLUMN_NAME])\n", "fcst_df.head()" @@ -823,38 +462,13 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "End of the data we trained on:\n", - "time_series_id\n", - "ts0 2000-01-02 05:00:00\n", - "ts1 2000-01-02 05:00:00\n", - "Name: date, dtype: datetime64[ns]\n", - "\n", - "Start of the data we want to predict on:\n", - "time_series_id\n", - "ts0 2000-01-02 18:00:00\n", - "ts1 2000-01-02 18:00:00\n", - "Name: date, dtype: datetime64[ns]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\sagoswami\\projects\\2024\\forecast_notebooks\\azureml-examples\\sdk\\python\\jobs\\automl-standalone-jobs\\automl-forecasting-forecast-function\\helper.py:39: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.\n", - " time_column_name: pd.date_range(\n" - ] - } - ], + "outputs": [], "source": [ "# Generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", "from helper import get_timeseries, make_forecasting_query\n", + "\n", "X_context, y_context, X_away, y_away = get_timeseries(\n", " train_len=42, # train data was 30 steps long\n", " test_len=4,\n", @@ -886,7 +500,7 @@ "source": [ "x_gap_test = X_away.copy()\n", "x_gap_test[\"y\"] = y_away\n", - "x_gap_test['data_type'] = 'test' # Dummy data\n", + "x_gap_test[\"data_type\"] = \"test\" # Dummy data\n", "\n", "x_gap_test.to_csv(\"./data/test_gap_scenario/gap_test_data.csv\")" ] @@ -911,38 +525,16 @@ "source": [ "gap_job = ml_client.batch_endpoints.invoke(\n", " endpoint_name=batch_endpoint_name,\n", - " input=my_test_data_gap_input, #Test data input\n", + " input=my_test_data_gap_input, # Test data input\n", " deployment_name=\"non-mlflow-deployment\", # name is required as default deployment is not set\n", ")" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running\n", - "RunId: batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65\n", - "Web View: https://ml.azure.com/runs/batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", - "\n", - "Streaming logs/azureml/executionlogs.txt\n", - "========================================\n", - "\n", - "[2024-10-28 18:28:54Z] Submitting 1 runs, first five are: 7805450f:418b0b81-c680-48f7-be2c-22e8add701f6\n", - "[2024-10-28 18:37:22Z] Completing processing run id 418b0b81-c680-48f7-be2c-22e8add701f6.\n", - "\n", - "Execution Summary\n", - "=================\n", - "RunId: batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65\n", - "Web View: https://ml.azure.com/runs/batchjob-291ce8ee-cff0-49f2-9335-107e2472fc65?wsid=/subscriptions/72c03bf3-4e69-41af-9532-dfcdc3eefef4/resourcegroups/aml-benchmarking/workspaces/aml-benchmarking-rd\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "job_name = gap_job.name\n", "batch_job = ml_client.jobs.get(name=job_name)\n", @@ -953,17 +545,9 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Downloading artifact azureml://datastores/workspaceblobstore/paths/azureml/418b0b81-c680-48f7-be2c-22e8add701f6/score/ to outputs\\gap_scenario\n" - ] - } - ], + "outputs": [], "source": [ "# Get the predictions\n", "gap_download_path = \"./outputs/gap_scenario/\"\n", @@ -972,123 +556,13 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "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", - "
Unnamed: 0dateext_predictortime_series_iddata_typeyprediction_intervalpredicted
0422000-01-02 18:00:0084ts0test42.253241[42.00682831806445, 42.87469034396139]42.440759
1432000-01-02 19:00:0085ts0test43.126377[42.884999905408854, 43.98595151391999]43.435476
2442000-01-02 20:00:0086ts0test44.532234[44.2439097269378, 44.61647444969405]44.430192
3452000-01-02 21:00:0087ts0test45.489004[45.124729420901716, 45.725087513033145]45.424908
4422000-01-02 18:00:0084ts1test47.127495[37.27487135377912, 38.14273337967606]37.708802
\n", - "
" - ], - "text/plain": [ - " Unnamed: 0 date ext_predictor time_series_id data_type \\\n", - "0 42 2000-01-02 18:00:00 84 ts0 test \n", - "1 43 2000-01-02 19:00:00 85 ts0 test \n", - "2 44 2000-01-02 20:00:00 86 ts0 test \n", - "3 45 2000-01-02 21:00:00 87 ts0 test \n", - "4 42 2000-01-02 18:00:00 84 ts1 test \n", - "\n", - " y prediction_interval predicted \n", - "0 42.253241 [42.00682831806445, 42.87469034396139] 42.440759 \n", - "1 43.126377 [42.884999905408854, 43.98595151391999] 43.435476 \n", - "2 44.532234 [44.2439097269378, 44.61647444969405] 44.430192 \n", - "3 45.489004 [45.124729420901716, 45.725087513033145] 45.424908 \n", - "4 47.127495 [37.27487135377912, 38.14273337967606] 37.708802 " - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gap_fcst_df = pd.read_csv(gap_download_path + output_file, parse_dates=[TIME_COLUMN_NAME])\n", + "outputs": [], + "source": [ + "gap_fcst_df = pd.read_csv(\n", + " gap_download_path + output_file, parse_dates=[TIME_COLUMN_NAME]\n", + ")\n", "gap_fcst_df.head()" ] }, @@ -1098,7 +572,7 @@ "metadata": {}, "outputs": [], "source": [ - "gap_fcst_df['data_type']=\"gap_forecast\"" + "gap_fcst_df[\"data_type\"] = \"gap_forecast\"" ] }, { @@ -1132,28 +606,15 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'azureml.training'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[3], line 5\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mmlflow\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01msklearn\u001b[39;00m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[1;32m----> 5\u001b[0m fitted_model \u001b[38;5;241m=\u001b[39m \u001b[43mmlflow\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msklearn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mload_model\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmlflow_dir\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\mlflow\\sklearn\\__init__.py:638\u001b[0m, in \u001b[0;36mload_model\u001b[1;34m(model_uri, dst_path)\u001b[0m\n\u001b[0;32m 636\u001b[0m sklearn_model_artifacts_path \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(local_model_path, flavor_conf[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpickled_model\u001b[39m\u001b[38;5;124m\"\u001b[39m])\n\u001b[0;32m 637\u001b[0m serialization_format \u001b[38;5;241m=\u001b[39m flavor_conf\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mserialization_format\u001b[39m\u001b[38;5;124m\"\u001b[39m, SERIALIZATION_FORMAT_PICKLE)\n\u001b[1;32m--> 638\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_load_model_from_local_file\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 639\u001b[0m \u001b[43m \u001b[49m\u001b[43mpath\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msklearn_model_artifacts_path\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mserialization_format\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mserialization_format\u001b[49m\n\u001b[0;32m 640\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mc:\\Users\\sagoswami\\.conda\\envs\\sdkv2-test1\\lib\\site-packages\\mlflow\\sklearn\\__init__.py:453\u001b[0m, in \u001b[0;36m_load_model_from_local_file\u001b[1;34m(path, serialization_format)\u001b[0m\n\u001b[0;32m 449\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(path, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrb\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[0;32m 450\u001b[0m \u001b[38;5;66;03m# Models serialized with Cloudpickle cannot necessarily be deserialized using Pickle;\u001b[39;00m\n\u001b[0;32m 451\u001b[0m \u001b[38;5;66;03m# That's why we check the serialization format of the model before deserializing\u001b[39;00m\n\u001b[0;32m 452\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m serialization_format \u001b[38;5;241m==\u001b[39m SERIALIZATION_FORMAT_PICKLE:\n\u001b[1;32m--> 453\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mpickle\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mload\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 454\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m serialization_format \u001b[38;5;241m==\u001b[39m SERIALIZATION_FORMAT_CLOUDPICKLE:\n\u001b[0;32m 455\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mcloudpickle\u001b[39;00m\n", - "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'azureml.training'" - ] - } - ], + "outputs": [], "source": [ "import mlflow.pyfunc\n", "import mlflow.sklearn\n", + "\n", "# Please ensure that the training artifacts are downloaded. For more details refer to the training notebook\n", "mlflow_dir = \"./artifact_downloads/outputs/mlflow-model\"\n", "fitted_model = mlflow.sklearn.load_model(mlflow_dir)" @@ -1167,7 +628,7 @@ }, "outputs": [], "source": [ - "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + "df_train[df_train[\"time_series_id\"] == \"ts1\"].tail(2)" ] }, { @@ -1178,7 +639,7 @@ }, "outputs": [], "source": [ - "df_test[df_test['time_series_id']==\"ts1\"].head(2)" + "df_test[df_test[\"time_series_id\"] == \"ts1\"].head(2)" ] }, { @@ -1286,6 +747,7 @@ "source": [ "# generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", "from helper import get_timeseries, make_forecasting_query\n", + "\n", "X_context, y_context, X_away, y_away = get_timeseries(\n", " train_len=42, # train data was 30 steps long\n", " test_len=4,\n", @@ -1396,7 +858,9 @@ "print(\"Forecast origin: \" + str(forecast_origin))\n", "\n", "# The model uses lags and rolling windows to look back in time\n", - "n_lookback_periods = max(lags) # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", + "n_lookback_periods = max(\n", + " lags\n", + ") # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", "lookback = pd.DateOffset(hours=n_lookback_periods)\n", "horizon = pd.DateOffset(hours=forecast_horizon)" ] @@ -1417,7 +881,7 @@ "# show the forecast request aligned\n", "X_show = X_pred.copy()\n", "X_show[TARGET_COLUMN_NAME] = y_pred\n", - "X_show[X_show['time_series_id']==\"ts0\"]" + "X_show[X_show[\"time_series_id\"] == \"ts0\"]" ] }, { @@ -1428,7 +892,9 @@ }, "outputs": [], "source": [ - "X_pred['data_type']=\"unknown\" # Our trining had an additional column called data_type, hence, adding it" + "X_pred[\n", + " \"data_type\"\n", + "] = \"unknown\" # Our trining had an additional column called data_type, hence, adding it" ] }, { @@ -1444,7 +910,9 @@ "\n", "# show the forecast aligned without the generated features\n", "X_show = xy_away.reset_index()\n", - "X_show[[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]] # prediction is in _automl_target_col" + "X_show[\n", + " [\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]\n", + "] # prediction is in _automl_target_col" ] }, { @@ -1464,7 +932,7 @@ }, "outputs": [], "source": [ - "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + "df_train[df_train[\"time_series_id\"] == \"ts1\"].tail(2)" ] }, { @@ -1483,7 +951,9 @@ }, "outputs": [], "source": [ - "X_show[X_show['time_series_id'] == \"ts1\"][[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]" + "X_show[X_show[\"time_series_id\"] == \"ts1\"][\n", + " [\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]\n", + "]" ] }, { diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb index 6e30abd5d5a..e0d50b2737a 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-local-inference.ipynb @@ -96,8 +96,10 @@ "import pandas as pd\n", "\n", "fitted_model = mlflow.sklearn.load_model(mlflow_dir)\n", - "df_train = pd.read_parquet('./data/training-mltable-folder/df_train.parquet') # We stored the training and test data during training\n", - "df_test = pd.read_parquet('./data/testing-mltable-folder/df_test.parquet')" + "df_train = pd.read_parquet(\n", + " \"./data/training-mltable-folder/df_train.parquet\"\n", + ") # We stored the training and test data during training\n", + "df_test = pd.read_parquet(\"./data/testing-mltable-folder/df_test.parquet\")" ] }, { @@ -108,7 +110,7 @@ }, "outputs": [], "source": [ - "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + "df_train[df_train[\"time_series_id\"] == \"ts1\"].tail(2)" ] }, { @@ -119,7 +121,7 @@ }, "outputs": [], "source": [ - "df_test[df_test['time_series_id']==\"ts1\"].head(2)" + "df_test[df_test[\"time_series_id\"] == \"ts1\"].head(2)" ] }, { @@ -227,6 +229,7 @@ "source": [ "# generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", "from helper import get_timeseries, make_forecasting_query\n", + "\n", "X_context, y_context, X_away, y_away = get_timeseries(\n", " train_len=42, # train data was 30 steps long\n", " test_len=4,\n", @@ -337,7 +340,9 @@ "print(\"Forecast origin: \" + str(forecast_origin))\n", "\n", "# The model uses lags and rolling windows to look back in time\n", - "n_lookback_periods = max(lags) # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", + "n_lookback_periods = max(\n", + " lags\n", + ") # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", "lookback = pd.DateOffset(hours=n_lookback_periods)\n", "horizon = pd.DateOffset(hours=forecast_horizon)" ] @@ -358,7 +363,7 @@ "# show the forecast request aligned\n", "X_show = X_pred.copy()\n", "X_show[TARGET_COLUMN_NAME] = y_pred\n", - "X_show[X_show['time_series_id']==\"ts0\"]" + "X_show[X_show[\"time_series_id\"] == \"ts0\"]" ] }, { @@ -369,7 +374,9 @@ }, "outputs": [], "source": [ - "X_pred['data_type']=\"unknown\" # Our trining had an additional column called data_type, hence, adding it" + "X_pred[\n", + " \"data_type\"\n", + "] = \"unknown\" # Our trining had an additional column called data_type, hence, adding it" ] }, { @@ -385,7 +392,9 @@ "\n", "# show the forecast aligned without the generated features\n", "X_show = xy_away.reset_index()\n", - "X_show[[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]] # prediction is in _automl_target_col" + "X_show[\n", + " [\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]\n", + "] # prediction is in _automl_target_col" ] }, { @@ -405,7 +414,7 @@ }, "outputs": [], "source": [ - "df_train[df_train['time_series_id']==\"ts1\"].tail(2)" + "df_train[df_train[\"time_series_id\"] == \"ts1\"].tail(2)" ] }, { @@ -424,7 +433,9 @@ }, "outputs": [], "source": [ - "X_show[X_show['time_series_id'] == \"ts1\"][[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]" + "X_show[X_show[\"time_series_id\"] == \"ts1\"][\n", + " [\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]\n", + "]" ] } ], diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb index 833c34bf868..310d56e0d8a 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb @@ -36,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "tags": [] }, @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "tags": [] }, @@ -80,7 +80,7 @@ " resource_group = \"\"\n", " workspace = \"\"\n", "\n", - " ml_client = MLClient(credential, subscription_id, resource_group, workspace)" + "ml_client = MLClient(credential, subscription_id, resource_group, workspace)" ] }, { @@ -113,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { "tags": [] }, @@ -186,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": { "tags": [] }, @@ -250,7 +250,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": { "tags": [] }, @@ -340,7 +340,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": { "tags": [] }, @@ -441,7 +441,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": { "tags": [] }, @@ -532,7 +532,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": { "tags": [] }, @@ -585,7 +585,7 @@ "friendly_name": "Forecasting away from training data", "index_order": 3, "kernelspec": { - "display_name": "sdkv2-base", + "display_name": "sdkv2-test1", "language": "python", "name": "python3" }, diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py index 2c6cbb98161..a657fb56d56 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py @@ -3,6 +3,7 @@ import pandas as pd import numpy as np + def get_timeseries( train_len: int, test_len: int, @@ -115,4 +116,4 @@ def make_forecasting_query( X_pred = pd.concat([X_past, X_future]) y_pred = np.concatenate([y_past, y_query]) - return X_pred, y_pred \ No newline at end of file + return X_pred, y_pred From 0ee9809755cdaf340919ba7d6e4fa3204cea13b4 Mon Sep 17 00:00:00 2001 From: Sampurna Goswami Date: Tue, 29 Oct 2024 01:22:34 +0530 Subject: [PATCH 4/6] Fix format issues --- ...ml-forecasting-function-gap-training.ipynb | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb index 310d56e0d8a..ab970e0479a 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-training.ipynb @@ -139,6 +139,7 @@ "n_test_periods = forecast_horizon\n", "\n", "from helper import get_timeseries\n", + "\n", "X_train, y_train, X_test, y_test = get_timeseries(\n", " train_len=n_train_periods,\n", " test_len=n_test_periods,\n", @@ -167,6 +168,7 @@ "source": [ "# Plot the example time series\n", "import matplotlib.pyplot as plt\n", + "\n", "whole_data = X_train.copy()\n", "target_label = \"y\"\n", "whole_data[target_label] = y_train\n", @@ -204,8 +206,8 @@ "outputs": [], "source": [ "# For vizualisation of the time series\n", - "df_train['data_type'] = 'Training' # Add a column to label training data\n", - "df_test['data_type'] = 'Testing' # Add a column to label testing data\n", + "df_train[\"data_type\"] = \"Training\" # Add a column to label training data\n", + "df_test[\"data_type\"] = \"Testing\" # Add a column to label testing data\n", "\n", "# Concatenate the training and testing DataFrames\n", "df_plot = pd.concat([df_train, df_test])\n", @@ -215,13 +217,19 @@ "ax = plt.gca() # Get current axis\n", "\n", "# Group by both 'data_type' and 'time_series_id'\n", - "for (data_type, time_series_id), df in df_plot.groupby(['data_type', 'time_series_id']):\n", - " df.plot(x='date', y=TARGET_COLUMN_NAME, label=f\"{data_type} - {time_series_id}\", ax=ax, legend=False)\n", + "for (data_type, time_series_id), df in df_plot.groupby([\"data_type\", \"time_series_id\"]):\n", + " df.plot(\n", + " x=\"date\",\n", + " y=TARGET_COLUMN_NAME,\n", + " label=f\"{data_type} - {time_series_id}\",\n", + " ax=ax,\n", + " legend=False,\n", + " )\n", "\n", "# Customize the plot\n", - "plt.xlabel('Date')\n", - "plt.ylabel('Value')\n", - "plt.title('Train and Test Data')\n", + "plt.xlabel(\"Date\")\n", + "plt.ylabel(\"Value\")\n", + "plt.title(\"Train and Test Data\")\n", "\n", "# Manually create the legend after plotting\n", "plt.legend(title=\"Data Type and Time Series ID\")\n", @@ -239,6 +247,7 @@ "import mltable\n", "import os\n", "\n", + "\n", "def create_ml_table(data_frame, file_name, output_folder):\n", " os.makedirs(output_folder, exist_ok=True)\n", " data_path = os.path.join(output_folder, file_name)\n", @@ -270,10 +279,10 @@ "\n", "my_training_data_input.__dict__\n", "\n", - "#Test data\n", + "# Test data\n", "os.makedirs(\"data\", exist_ok=True)\n", "create_ml_table(\n", - " X_test, #df_test,\n", + " X_test, # df_test,\n", " \"X_test.parquet\",\n", " \"./data/testing-mltable-folder\",\n", ")\n", @@ -429,6 +438,7 @@ "outputs": [], "source": [ "import mlflow\n", + "\n", "MLFLOW_TRACKING_URI = ml_client.workspaces.get(\n", " name=ml_client.workspace_name\n", ").mlflow_tracking_uri\n", @@ -540,6 +550,7 @@ "source": [ "# Create local folder\n", "import os\n", + "\n", "local_dir = \"./artifact_downloads\"\n", "if not os.path.exists(local_dir):\n", " os.mkdir(local_dir)" From 6f31b52ba8a011fb32f2d8ca884a0c80b34093a1 Mon Sep 17 00:00:00 2001 From: Sampurna Goswami Date: Tue, 29 Oct 2024 19:27:25 +0530 Subject: [PATCH 5/6] Working batch inf e2e --- ...casting-function-gap-batch-inference.ipynb | 478 ++++-------------- .../helper.py | 5 +- 2 files changed, 105 insertions(+), 378 deletions(-) diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb index c855474e3e1..4d533be14c7 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 9, "metadata": { "tags": [] }, @@ -91,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -123,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -199,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -255,13 +255,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ + "# We stored the training and test data during training\n", "df_train = pd.read_parquet(\n", " \"./data/training-mltable-folder/df_train.parquet\"\n", - ") # We stored the training and test data during training\n", + ") \n", "df_test = pd.read_parquet(\"./data/testing-mltable-folder/df_test.parquet\")" ] }, @@ -283,6 +284,17 @@ "df_test.head(2)" ] }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "# The above test data follows the training data\n", + "# Store in folder for the batch endpoint to use as parquet file\n", + "df_test.to_parquet(\"./data/test_data_scenarios/df_test_scenario1.parquet\")" + ] + }, { "cell_type": "code", "execution_count": null, @@ -346,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -377,16 +389,16 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# import os\n", - "# os.system(\"az login\")\n", - "\n", - "# def refresh_token():\n", - "# os.system(\"az account get-access-token\")\n", + "import os\n", + "os.system(\"az login\")\n", "\n", + "def refresh_token():\n", + " os.system(\"az account get-access-token\")\n", + "refresh_token()\n", "# Call refresh_token() at regular intervals in your notebook, if necessary" ] }, @@ -494,7 +506,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -505,42 +517,31 @@ "x_gap_test.to_csv(\"./data/test_gap_scenario/gap_test_data.csv\")" ] }, - { - "cell_type": "code", - "execution_count": 57, - "metadata": {}, - "outputs": [], - "source": [ - "my_test_data_gap_input = Input(\n", - " type=AssetTypes.URI_FOLDER,\n", - " path=\"./data/test_gap_scenario/\", # Path to the data folder that has the test data with gap\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "metadata": {}, - "outputs": [], - "source": [ - "gap_job = ml_client.batch_endpoints.invoke(\n", - " endpoint_name=batch_endpoint_name,\n", - " input=my_test_data_gap_input, # Test data input\n", - " deployment_name=\"non-mlflow-deployment\", # name is required as default deployment is not set\n", - ")" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "job_name = gap_job.name\n", - "batch_job = ml_client.jobs.get(name=job_name)\n", - "print(batch_job.status)\n", - "# stream the job logs\n", - "ml_client.jobs.stream(name=job_name)" + "# Since the length of the lookback is 3, we need to add 3 periods from the context to the request so that the model has the data it needs\n", + "\n", + "# Put the X and y back together for a while. They like each other and it makes them happy.\n", + "X_context[TARGET_COLUMN_NAME] = y_context\n", + "X_away[TARGET_COLUMN_NAME] = y_away\n", + "fulldata = pd.concat([X_context, X_away])\n", + "\n", + "# Forecast origin is the last point of data, which is one 1-hr period before test\n", + "forecast_origin = X_away[TIME_COLUMN_NAME].min() - pd.DateOffset(hours=1)\n", + "# it is indeed the last point of the context\n", + "assert forecast_origin == X_context[TIME_COLUMN_NAME].max()\n", + "print(\"Forecast origin: \" + str(forecast_origin))\n", + "\n", + "# The model uses lags and rolling windows to look back in time\n", + "n_lookback_periods = max(\n", + " lags\n", + ") # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", + "lookback = pd.DateOffset(hours=n_lookback_periods)\n", + "horizon = pd.DateOffset(hours=forecast_horizon)" ] }, { @@ -549,370 +550,123 @@ "metadata": {}, "outputs": [], "source": [ - "# Get the predictions\n", - "gap_download_path = \"./outputs/gap_scenario/\"\n", - "ml_client.jobs.download(job_name, download_path=gap_download_path)" + "# Now make the forecast query from context. This is the main thing for predicting gap data\n", + "from helper import make_forecasting_query\n", + "X_pred, y_pred = make_forecasting_query(\n", + " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", + ")\n", + "\n", + "# show the forecast request aligned\n", + "X_show = X_pred.copy()\n", + "X_show[TARGET_COLUMN_NAME] = y_pred\n", + "X_show[X_show[\"time_series_id\"] == \"ts0\"]" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 51, "metadata": {}, "outputs": [], "source": [ - "gap_fcst_df = pd.read_csv(\n", - " gap_download_path + output_file, parse_dates=[TIME_COLUMN_NAME]\n", - ")\n", - "gap_fcst_df.head()" + "X_pred[\n", + " \"data_type\"\n", + "] = \"unknown\" # Our trining had an additional column called data_type, hence, adding it" ] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 54, "metadata": {}, "outputs": [], "source": [ - "gap_fcst_df[\"data_type\"] = \"gap_forecast\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Local inferencing from model pickle\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "1. **Model** \n", - " We will need the MLflow model, which is downloaded at the end of the training notebook. Follow any training notebook to get the model. The MLflow model is usually downloaded to the folder: `./artifact_downloads/outputs/mlflow-model`.\n", - "\n", - "2. **Environment** \n", - " We will need the environment to load the model. Please run the following commands to create the environment (the conda file is usually downloaded to: `./artifact_downloads/outputs/mlflow-model/conda.yaml`):\n", - " - `conda env create --file `\n", - " - `conda activate project_environment`\n", - "\n", - "3. **Register environment as kernel** \n", - " - Please run the following command to register the environment as a kernel: \n", - " ```bash\n", - " python -m ipykernel install --user --name project_environment --display-name \"model-inference\"\n", - " ```\n", - " - Refresh the kernel and then select the newly created kernel named `model-inference` from the kernel dropdown.\n", - " \n", - " Now we are good to run this notebook in the newly created kernel." + "gap_data = X_pred.copy()\n", + "gap_data[TARGET_COLUMN_NAME] = y_pred\n", + "gap_data.to_csv(\"./data/test_gap_scenario/gap_data_with_context.csv\")" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ - "import mlflow.pyfunc\n", - "import mlflow.sklearn\n", - "\n", - "# Please ensure that the training artifacts are downloaded. For more details refer to the training notebook\n", - "mlflow_dir = \"./artifact_downloads/outputs/mlflow-model\"\n", - "fitted_model = mlflow.sklearn.load_model(mlflow_dir)" + "y_pred" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, + "execution_count": 55, + "metadata": {}, "outputs": [], "source": [ - "df_train[df_train[\"time_series_id\"] == \"ts1\"].tail(2)" + "my_test_data_gap_input = Input(\n", + " type=AssetTypes.URI_FOLDER,\n", + " path=\"./data/test_gap_scenario/\", # Path to the data folder that has the test data with gap\n", + ")" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "df_test[df_test[\"time_series_id\"] == \"ts1\"].head(2)" - ] - }, - { - "cell_type": "markdown", + "execution_count": 60, "metadata": {}, - "source": [ - "# Forecasting from the trained model\n", - "\n", - "In this section we will review the forecast interface for two main scenarios: forecasting right after the training data, and the more complex interface for forecasting when there is a gap (in the time sense) between training and testing data.\n", - "\n", - "## X_train is directly followed by the X_test\n", - "Let's first consider the case when the prediction period immediately follows the training data. This is typical in scenarios where we have the time to retrain the model every time we wish to forecast. Forecasts that are made on daily and slower cadence typically fall into this category. Retraining the model every time benefits the accuracy because the most recent data is often the most informative.\n", - "\n", - "\n", - "\"Description\"\n", - "\n", - "We use X_test as a forecast request to generate the predictions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, "outputs": [], "source": [ - "X_test = df_test.copy()\n", - "y_test = X_test.pop(TARGET_COLUMN_NAME).values.astype(float)\n", + "import os\n", + "os.system(\"az login\")\n", "\n", - "y_pred_no_gap, xy_nogap = fitted_model.forecast(X_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "### Confidence Intervals\n", - "Forecasting model may be used for the prediction of forecasting intervals by running forecast_quantiles(). This method accepts the same parameters as forecast()." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "quantiles = fitted_model.forecast_quantiles(X_test)\n", - "quantiles" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "### Distribution forecasts\n", - "Often the figure of interest is not just the point prediction, but the prediction at some quantile of the distribution. This arises when the forecast is used to control some kind of inventory, for example of grocery items or virtual machines for a cloud service. In such case, the control point is usually something like \"we want the item to be in stock and not run out 99% of the time\". This is called a \"service level\". Here is how you get quantile forecasts." + "def refresh_token():\n", + " os.system(\"az account get-access-token\")\n", + "refresh_token()\n", + "# Call refresh_token() at regular intervals in your notebook, if necessary" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# Specify which quantiles you would like\n", - "fitted_model.quantiles = [0.01, 0.5, 0.95]\n", - "\n", - "# use forecast_quantiles function, not the forecast() one\n", - "y_pred_quantiles = fitted_model.forecast_quantiles(X_test)\n", - "\n", - "# quantile forecasts returned in a Dataframe along with the time and time series id columns\n", - "y_pred_quantiles" - ] - }, - { - "cell_type": "markdown", + "execution_count": 61, "metadata": {}, - "source": [ - "# Forecasting away from training data\n", - "Suppose we trained a model, some time passed, and now we want to apply the model without re-training. If the model \"looks back\" -- uses previous values of the target -- then we somehow need to provide those values to the model.\n", - "\n", - "\"Description\"\n", - "\n", - "The notion of forecast origin comes into play: **the forecast origin is the last period for which we have seen the target value.** This applies per time-series, so each time-series can have a different forecast origin.\n", - "\n", - "The part of data before the forecast origin is the **prediction context**. To provide the context values the model needs when it looks back, we pass definite values in y_test (aligned with corresponding times in X_test)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, "outputs": [], "source": [ - "# generate the same kind of test data we trained on, but now make the train set much longer, so that the test set will be in the future\n", - "from helper import get_timeseries, make_forecasting_query\n", - "\n", - "X_context, y_context, X_away, y_away = get_timeseries(\n", - " train_len=42, # train data was 30 steps long\n", - " test_len=4,\n", - " time_column_name=TIME_COLUMN_NAME,\n", - " target_column_name=TARGET_COLUMN_NAME,\n", - " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", - " time_series_number=2,\n", - ")\n", - "\n", - "print(\"End of the data we trained on:\")\n", - "print(df_train.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())\n", - "\n", - "print(\"\\nStart of the data we want to predict on:\")\n", - "print(X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is a gap of 12 hours between end of training and beginning of X_away. (It looks like 13 because all timestamps point to the start of the one hour periods.) Using only X_away will fail without adding context data for the model to consume" + "gap_job = ml_client.batch_endpoints.invoke(\n", + " endpoint_name=batch_endpoint_name,\n", + " input=my_test_data_gap_input, # Test data input\n", + " deployment_name=\"non-mlflow-deployment\", # name is required as default deployment is not set\n", + ")" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "try:\n", - " y_pred_away, xy_away = fitted_model.forecast(X_away)\n", - " xy_away\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of y (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define y value to establish the forecast origin.\n", - "\n", - "We will use the helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." - ] - }, - { - "cell_type": "markdown", "metadata": {}, - "source": [ - "Let's see where the context data ends - it ends, by construction, just before the testing data starts." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, "outputs": [], "source": [ - "print(\n", - " X_context.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", - " [\"min\", \"max\", \"count\"]\n", - " )\n", - ")\n", - "print(\n", - " X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", - " [\"min\", \"max\", \"count\"]\n", - " )\n", - ")\n", - "X_context.tail(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of y (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define y value to establish the forecast origin.\n", - "\n", - "We will use this helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." + "job_name = gap_job.name\n", + "batch_job = ml_client.jobs.get(name=job_name)\n", + "print(batch_job.status)\n", + "# stream the job logs\n", + "ml_client.jobs.stream(name=job_name)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ - "# Since the length of the lookback is 3, we need to add 3 periods from the context to the request so that the model has the data it needs\n", - "\n", - "# Put the X and y back together for a while. They like each other and it makes them happy.\n", - "X_context[TARGET_COLUMN_NAME] = y_context\n", - "X_away[TARGET_COLUMN_NAME] = y_away\n", - "fulldata = pd.concat([X_context, X_away])\n", - "\n", - "# Forecast origin is the last point of data, which is one 1-hr period before test\n", - "forecast_origin = X_away[TIME_COLUMN_NAME].min() - pd.DateOffset(hours=1)\n", - "# it is indeed the last point of the context\n", - "assert forecast_origin == X_context[TIME_COLUMN_NAME].max()\n", - "print(\"Forecast origin: \" + str(forecast_origin))\n", - "\n", - "# The model uses lags and rolling windows to look back in time\n", - "n_lookback_periods = max(\n", - " lags\n", - ") # n_lookback_periods = max(max(lags), forecast_horizon) # If target_rolling_window_size is used\n", - "lookback = pd.DateOffset(hours=n_lookback_periods)\n", - "horizon = pd.DateOffset(hours=forecast_horizon)" + "# Get the predictions\n", + "gap_download_path = \"./outputs/gap_scenario/\"\n", + "ml_client.jobs.download(job_name, download_path=gap_download_path)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ - "# now make the forecast query from context (refer to figure)\n", - "X_pred, y_pred = make_forecasting_query(\n", - " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", + "gap_fcst_df = pd.read_csv(\n", + " gap_download_path + output_file, parse_dates=[TIME_COLUMN_NAME]\n", ")\n", - "\n", - "# show the forecast request aligned\n", - "X_show = X_pred.copy()\n", - "X_show[TARGET_COLUMN_NAME] = y_pred\n", - "X_show[X_show[\"time_series_id\"] == \"ts0\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "X_pred[\n", - " \"data_type\"\n", - "] = \"unknown\" # Our trining had an additional column called data_type, hence, adding it" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# Now everything should work\n", - "y_pred_away, xy_away = fitted_model.forecast(X_pred, y_pred)\n", - "\n", - "# show the forecast aligned without the generated features\n", - "X_show = xy_away.reset_index()\n", - "X_show[\n", - " [\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]\n", - "] # prediction is in _automl_target_col" + "gap_fcst_df" ] }, { @@ -921,45 +675,19 @@ "metadata": {}, "outputs": [], "source": [ - "### Let us look at the tail of training data and the head of the test data for one grain" + "gap_data" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "df_train[df_train[\"time_series_id\"] == \"ts1\"].tail(2)" - ] - }, - { - "cell_type": "markdown", "metadata": {}, - "source": [ - "If there is a gap between the train and the test data, and the test data uses lags/ rolling forecasts, we need to append the context data such that the test data has access to the lags\n", - "In the above case, train_data ends at 2000-01-02 05:00:00" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, "outputs": [], "source": [ - "X_show[X_show[\"time_series_id\"] == \"ts1\"][\n", - " [\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]\n", - "]" + "# show the forecast aligned without the generated features\n", + "X_show = gap_fcst_df[gap_fcst_df['data_type']==\"test\"]\n", + "X_show" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] } ], "metadata": { diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py index a657fb56d56..c916b304b5e 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py @@ -3,7 +3,6 @@ import pandas as pd import numpy as np - def get_timeseries( train_len: int, test_len: int, @@ -107,7 +106,7 @@ def make_forecasting_query( # Now take y_future and turn it into question marks y_query = y_future.copy().astype(float) # because sometimes life hands you an int - y_query.fill(np.NaN) + y_query.fill(np.nan) print("X_past is " + str(X_past.shape) + " - shaped") print("X_future is " + str(X_future.shape) + " - shaped") @@ -116,4 +115,4 @@ def make_forecasting_query( X_pred = pd.concat([X_past, X_future]) y_pred = np.concatenate([y_past, y_query]) - return X_pred, y_pred + return X_pred, y_pred \ No newline at end of file From 1fb52e039260f66ca40c0df764bcb1c3cd724c0b Mon Sep 17 00:00:00 2001 From: Sampurna Goswami Date: Tue, 29 Oct 2024 19:47:09 +0530 Subject: [PATCH 6/6] Fix formatting issues --- ...casting-function-gap-batch-inference.ipynb | 38 ++----------------- .../helper.py | 3 +- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb index 4d533be14c7..e40fea60251 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/auto-ml-forecasting-function-gap-batch-inference.ipynb @@ -79,7 +79,6 @@ "outputs": [], "source": [ "from mlflow.tracking.client import MlflowClient\n", - "from mlflow.artifacts import download_artifacts\n", "\n", "# Set the MLFLOW TRACKING URI\n", "mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)\n", @@ -260,9 +259,7 @@ "outputs": [], "source": [ "# We stored the training and test data during training\n", - "df_train = pd.read_parquet(\n", - " \"./data/training-mltable-folder/df_train.parquet\"\n", - ") \n", + "df_train = pd.read_parquet(\"./data/training-mltable-folder/df_train.parquet\")\n", "df_test = pd.read_parquet(\"./data/testing-mltable-folder/df_test.parquet\")" ] }, @@ -387,21 +384,6 @@ "### Invoke the endpoint with the test data" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.system(\"az login\")\n", - "\n", - "def refresh_token():\n", - " os.system(\"az account get-access-token\")\n", - "refresh_token()\n", - "# Call refresh_token() at regular intervals in your notebook, if necessary" - ] - }, { "cell_type": "code", "execution_count": 27, @@ -552,6 +534,7 @@ "source": [ "# Now make the forecast query from context. This is the main thing for predicting gap data\n", "from helper import make_forecasting_query\n", + "\n", "X_pred, y_pred = make_forecasting_query(\n", " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", ")\n", @@ -605,21 +588,6 @@ ")" ] }, - { - "cell_type": "code", - "execution_count": 60, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.system(\"az login\")\n", - "\n", - "def refresh_token():\n", - " os.system(\"az account get-access-token\")\n", - "refresh_token()\n", - "# Call refresh_token() at regular intervals in your notebook, if necessary" - ] - }, { "cell_type": "code", "execution_count": 61, @@ -685,7 +653,7 @@ "outputs": [], "source": [ "# show the forecast aligned without the generated features\n", - "X_show = gap_fcst_df[gap_fcst_df['data_type']==\"test\"]\n", + "X_show = gap_fcst_df[gap_fcst_df[\"data_type\"] == \"test\"]\n", "X_show" ] } diff --git a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py index c916b304b5e..5420088a509 100644 --- a/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py +++ b/sdk/python/jobs/automl-standalone-jobs/automl-forecasting-forecast-function/helper.py @@ -3,6 +3,7 @@ import pandas as pd import numpy as np + def get_timeseries( train_len: int, test_len: int, @@ -115,4 +116,4 @@ def make_forecasting_query( X_pred = pd.concat([X_past, X_future]) y_pred = np.concatenate([y_past, y_query]) - return X_pred, y_pred \ No newline at end of file + return X_pred, y_pred