{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Bike-Share Demand Forecasting 2a: Modelling with [Amazon Forecast](https://aws.amazon.com/forecast/)\n", "\n", "이전 [1_Data_Preparation](1_Data_Preparation.ipynb) 노트북에서 수행한 bike-share 수요 예측 문제를 해결하기 위해 3가지 방법을 살펴봅니다.\n", "\n", "1. AWS \"Managed AI\"서비스 ([Amazon Forecast] (https://aws.amazon.com/forecast/))으로 일반적/규격화된 비즈니스 문제를 다룹니다.\n", "2. SageMaker의 built-in된 알고리즘 ([DeepAR] (https://docs.aws.amazon.com/sagemaker/latest/dg/deepar.html))을 사용하여 1번과 동일한 비즈니스 문제를 다룹니다.\n", "3. custom SageMaker 알고리즘을 사용하여 부가적인 차별적 SageMaker의 기능을 활용하면서 핵심 모델링을 수행합니다.\n", "\n", "\n", "**이 노트북은 AWS 콘솔을 통해 Amazon Forecast 서비스를 적용하는 방법을 보여 주지만 대신 동일한 작업을 모두 API를 통해 수행 할 수 있습니다.**\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dependencies and configuration\n", "\n", "라이브러리를 로딩한 다음, 설정값을 정의하고, AWS SDKs에 연결합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Basic data configuration is initialised and stored in the Data Preparation notebook\n", "# ...We just retrieve it here:\n", "%store -r\n", "assert bucket, \"Variable `bucket` missing from IPython store\"\n", "\n", "assert data_prefix, \"Variable `data_prefix` missing from IPython store\"\n", "assert target_train_filename, \"Variable `target_train_filename` missing from IPython store\"\n", "assert target_test_filename, \"Variable `target_test_filename` missing from IPython store\"\n", "assert related_filename, \"Variable `related_filename` missing from IPython store\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 1\n", "\n", "# Built-Ins:\n", "from datetime import datetime, timedelta\n", "\n", "# External Dependencies:\n", "import boto3\n", "from IPython.core.display import display, HTML\n", "import pandas as pd\n", "\n", "# Local Dependencies:\n", "%aimport util" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "session = boto3.Session() \n", "region = session.region_name\n", "forecast = session.client(service_name=\"forecast\") \n", "forecast_query = session.client(service_name=\"forecastquery\")\n", "s3 = session.client(service_name=\"s3\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overview\n", "\n", "아래 요약 내용을 보면, Amazon Forecast의 전체 워크플로우는 전형적인 Batch ML 모델 학습 접근 방식입니다.\n", "위에서 초기화한 forecast의 SDK는 AWS Console에서 수행하는 모든 단계의 Amazon Forecast 수행을 프로그래밍 방식으로 지원합니다. 여기에서는 **AWS Console** 방식의 사용방법을 알려드립니다,\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Selecting the Amazon Forecast domain\n", "\n", "Amazon Forecast는 몇가지 **domains** (documented [here](https://docs.aws.amazon.com/forecast/latest/dg/howitworks-domains-ds-types.html))들을 정의합니다.\n", "\n", "domain은 특정 use case에 맞도록 조정된 **기본 데이터 스키마**와 feature화 모델 아키텍처들을 제공합니다. 또한 custom 데이터 필드를 추가할 수 있습니다. 하지만, 일반적으로 기본 domain model에서 구조를 활용할 수 있는 장점이 많을수록, 더 나은 모델 성능을 얻을 수 있습니다.\n", "\n", "\n", "\n", "\n", "이번 예제에서는 [`RETAIL`](https://docs.aws.amazon.com/forecast/latest/dg/retail-domain.html) domain을 사용합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Preparing the data\n", "\n", "[domain documentation](https://docs.aws.amazon.com/forecast/latest/dg/retail-domain.html)에서 제공할 필요가 있는 필수 필드가 무엇인지 알 수 있습니다. 데이터를 약간 조정한 다음 다시 S3로 업로드하시면 됩니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target_train_df = pd.read_csv(f\"./data/{target_train_filename}\")\n", "target_test_df = pd.read_csv(f\"./data/{target_test_filename}\")\n", "related_df = pd.read_csv(f\"./data/{related_filename}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Retail domain 에서는 Target timeseries 데이터셋에는 컬럼 이름은 `timestamp`, `item_id`, `demand`로 정해져 있으며, 다른필드는 없어야 합니다.\n", "우리가 이전 노트북에서 작업한 데이터셋은 customer_type필드를 item_id로 변경하는 것 외에는 이미 위 기준에 적합하게 작업을 했습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target_train_df.rename(columns={ \"customer_type\": \"item_id\" }, inplace=True)\n", "target_test_df.rename(columns={ \"customer_type\": \"item_id\" }, inplace=True)\n", "target_train_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Retail 도메인에서 Related timeseries는:\n", "\n", "1. (우리가 이미 가지고 있는) `timestamp`이 포함되어 있으며,\n", "2. (날씨 정보가 customer_type에 따라 다르지 않지만) `item_id` 가 추가되어야 합니다.\n", "3. 여러 optional 한 domain 필드를 기본적으로 제안하고 있지만, 이 값들은 현재 데이터셋과 크게 관련이 없기에 변경합니다.\n", "\n", "추가적으로 일반적인 데이터셋에 대해서는 아래 사항을 고려하시기 바랍니다:\n", "\n", "4. Forecast에서 사용하는 [reserved field names](https://docs.aws.amazon.com/forecast/latest/dg/reserved-field-names.html) (including `temp`)는 컬럼에 사용할 수 없습니다.\n", "5. 사용자가 추가하는 fields의 [schema](https://docs.aws.amazon.com/forecast/latest/dg/API_SchemaAttribute.html)는 `string`, `integer`, `float`, `timestamp` 타입으로 구성할 수 있습니다.\n", "\n", "boolean 필드에 대해서는 string 형태로 데이터를 로드하면 동일한 결과를 얻을 수 있습니다.\n", "따라서 다음과 같이 데이터를 준비합니다.\n", "\n", "* Related timeseries 데이터에 `item_id` 추가합니다. (2번 항목)\n", "* 컬럼 `temp`를 `temperature`로 이름을 변경합니다.(4번 항목)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Duplicate data for each item_id in the target dataframe:\n", "related_peritem_dfs = []\n", "item_ids = target_train_df[\"item_id\"].unique()\n", "for item_id in item_ids:\n", " df = related_df.copy()\n", " df[\"item_id\"] = item_id\n", " related_peritem_dfs.append(df)\n", "\n", "related_df = pd.concat(related_peritem_dfs).sort_values([\"timestamp\", \"item_id\"]).reset_index(drop=True)\n", "\n", "# Rename any reserved columns to keep Forecast happy:\n", "related_df.rename(columns={ \"temp\": \"temperature\" }, inplace=True)\n", "related_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "...Amazon Forecast로 가져올 준비가 된 S3에 데이터를 저장합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Writing dataframes to file...\")\n", "!mkdir -p ./data/amzforecast\n", "target_train_df.to_csv(\n", " f\"./data/amzforecast/{target_train_filename}\",\n", " index=False\n", ")\n", "target_test_df.to_csv(\n", " f\"./data/amzforecast/{target_test_filename}\",\n", " index=False\n", ")\n", "related_df.to_csv(\n", " f\"./data/amzforecast/{related_filename}\",\n", " index=False\n", ")\n", "\n", "print(\"Uploading dataframes to S3...\")\n", "s3.upload_file(\n", " Filename=f\"./data/amzforecast/{target_train_filename}\",\n", " Bucket=bucket,\n", " Key=f\"{data_prefix}amzforecast/{target_train_filename}\"\n", ")\n", "print(f\"s3://{bucket}/{data_prefix}amzforecast/{target_train_filename}\")\n", "s3.upload_file(\n", " Filename=f\"./data/amzforecast/{target_test_filename}\",\n", " Bucket=bucket,\n", " Key=f\"{data_prefix}amzforecast/{target_test_filename}\"\n", ")\n", "print(f\"s3://{bucket}/{data_prefix}amzforecast/{target_test_filename}\")\n", "s3.upload_file(\n", " Filename=f\"./data/amzforecast/{related_filename}\",\n", " Bucket=bucket,\n", " Key=f\"{data_prefix}amzforecast/{related_filename}\"\n", ")\n", "print(f\"s3://{bucket}/{data_prefix}amzforecast/{related_filename}\")\n", "print(\"Done\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Create a Dataset Group\n", "\n", "이미 이전에 선택한 `region` 에서 Amazon Forecast console을 엽니다. 아래에 페이지가 표시되거나, 이전에 서비스를 사용한 적이 있으면 다른 대시보드가 표시될 수 있습니다. 아래와 같은 landing 페이지나 또는 왼쪽 메뉴에 있는 \"Dataset Groups\"에서 \"Create Dataset Group\"을 클릭합니다.\n", "\n", "> **Create dataset group**\n", " - Dataset group name : **`bikeshare_dataset_group`**\n", " - Forecasting domain : **`Retail`**\n", "\n", "**Next** 버튼을 클릭합니다.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "우선, 데이터프레임의 구조를 검토합니다:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target_train_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Create a Target Dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iam = boto3.client('iam')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iam_arn= [iam_role['Arn'] for iam_role in iam.list_roles(PathPrefix='/service-role/')['Roles'] \\\n", " if 'ForecastExecRole' in iam_role['RoleName']]\n", "print(\"Custom IAM role ARN 값은 아래 한 줄을 copy해서 사용하세요. \\n{}\".format(iam_arn[0]) )\n", "print(\"Data location 값은 아래 한 줄을 copy해서 사용하세요\")\n", "print(f\"s3://{bucket}/{data_prefix}amzforecast/{target_train_filename}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "아래와 같은 형식으로 target 데이터셋을 생성하는 메시지가 표시됩니다. (그렇지 않을 경우 대시보드에서 target 데이터셋을 작성하도록 선택할 수 있습니다. )\n", "\n", "> **Create target time series dataset**\n", " - Dataset name : **`bikeshare_target_dataset`**\n", " - Frequency of your data : **`hourly`**\n", " - Data schema : **`Schema builder에서 그림과 같이 timestamp와 item_id 순서를 변경`**\n", " - Timestamp format : **`yyyy-MM-dd HH:mm:ss`** (변경이 필요없이 default 값 사용)\n", " \n", "> **Dataset import details**\n", " - Dataset import name : **`bikeshare_target_import`**\n", " - Select time zone : **`Do not use time zone (변경해도 무관)`**\n", " - Data location : **`위 출력된 S3 URI값을 copy해서 사용합니다.`**\n", " - IAM role : **`Enter a custom IAM role ARN으로 변경 후 위 출력된 값을 copy해서 사용합니다.`**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Amazon Forecast는 리소스를 실행한 다음 데이터 셋의 유효성을 검사하는 과정을 수행하므로 Dataset import 작업을 완료하는데 몇 분의 시간이 걸립니다. (해당 데이터셋에서 10~15분 소요)\n", "* Target data import가 작업되는 동안 별도 대기 없이 다음 단계의 Related data import 수행을 바로 하시면 됩니다.\n", "* Target data를 import한 다음 \"predictor\" (forecast 모델)의 학습이 가능합니다.하지만, related data를 활용하다면 더욱 성능을 높일 수 있기에 related data가 import 된 다음에 'predictor'를 수행합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Create and import Related Timeseries Dataset\n", "\n", "다음 단계는 related dataset을 create하고 import 하는 과정입니다.\n", "아래 related data의 구조를 검토합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "related_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Create related time series dataset**\n", " - Dataset name : **`bikeshare_related_dataset`**\n", " - Frequency of your data : **`hourly`**\n", " - Data schema : **`Schema builder 대신 JSON schema로 변경한 다음, 아래 schema를 copy하여 기존 내용을 지운 후 붙여넣기 합니다.`**\n", "```json\n", "{\n", " \"Attributes\": [\n", " {\n", " \"AttributeName\": \"timestamp\",\n", " \"AttributeType\": \"timestamp\"\n", " },\n", " {\n", " \"AttributeName\": \"season\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"holiday\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"weekday\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"workingday\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"weathersit\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"temperature\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"atemp\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"hum\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"windspeed\",\n", " \"AttributeType\": \"float\"\n", " },\n", " {\n", " \"AttributeName\": \"item_id\",\n", " \"AttributeType\": \"string\"\n", " }\n", " ]\n", "}\n", "```\n", " - Timestamp format : **`yyyy-MM-dd HH:mm:ss`** (변경이 필요없이 default 값 사용)\n", "\n", "API docs는 각 [SchemaAttribute](https://docs.aws.amazon.com/forecast/latest/dg/API_SchemaAttribute.html) 가 포함할 수 있는 low-level의 상세 내용과 전체 [Schema](https://docs.aws.amazon.com/forecast/latest/dg/API_Schema.html) 객체를 제공합니다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "데이터셋 작업이 완료되면, 다음 데이터셋 import job을 수행합니다.\n", "\n", "> **Dataset import details**\n", " - Dataset import name : **`bikeshare_related_import`**\n", " - Data location : **`아래 출력된 값을 copy해서 사용합니다.`**\n", " - Custom IAM role ARN : **`arn:aws:iam::XXXXXXXX:role/service-role/ForecastDemoLab-XXX`** (변경이 필요없이 default 값 사용)\n", "\n", "\n", "\"Start import\" 를 수행하게 되면, 데이터가 로드되는 동안 대시보드 화면으로 돌아가게 됩니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Data location 값은 아래 한 줄을 copy해서 사용하세요\")\n", "print(f\"s3://{bucket}/{data_prefix}amzforecast/{related_filename}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 6: While the datasets import...\n", "\n", "데이터의 볼륨에 따라 import는 수분 이상 걸릴 수 있습니다.\n", "\n", "시간이 오래 걸리는 경우에는 [SageMaker에서 모델을 학습하는 노트북](2b_SageMaker_Built-In_DeepAR.ipynb)을 수행해 보는 것도 하나의 방법입니다.\n", "\n", "참고: 일반적으로는 대시보드에서 실시간 업데이트를 하지만, 최신 상태를 보기 위해 Amazon Forecast의 대시보드를 새로고침해야 할 수도 있습니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 7: Train a AutoPredictor\n", "\n", "아래 대시보드에서 Target과 Related 데이터 import가 완료되면, predictor 학습을 시작할 준비가 되었습니다.\n", "\n", "\n", "Start 버튼을 클릭하여 Predictor 학습을 실행합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ** Train predictor** \n", " - Predictor name: **`bikeshare_autopredictor`**\n", " - Forecast frequency: **`hour`**(원본 데이터의 frequency에 맞게 준비합니다)\n", " - Forecast horizon: **`336`** (2 weeks at 24hrs/day)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "먼저 테스트 데이터를 데이터셋 종단에서 짜른 target series 데이터의 크기를 검토합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "> ** Input data configuration ** \n", " - Holidays: **`Enable holidays 활성화`**\n", " - Country for holidays: **`United States`**(현재 데이터셋은 미국 기준입니다)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_train_samples = len(target_train_df[\"timestamp\"].unique())\n", "n_test_samples = len(target_test_df[\"timestamp\"].unique())\n", "n_related_samples = len(related_df[\"timestamp\"].unique())\n", "\n", "print(f\" {n_train_samples} training samples\")\n", "print(f\"+ {n_test_samples} testing samples\")\n", "print(f\"= {n_related_samples} total samples (related dataset)\")\n", "\n", "assert (\n", " n_train_samples + n_test_samples == n_related_samples\n", "), \"Mismatch between target train+test timeseries and related timeseries coverage\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[`BackTestWindowOffset`](https://docs.aws.amazon.com/forecast/latest/dg/API_EvaluationParameters.html#forecast-Type-EvaluationParameters-BackTestWindowOffset) 파라미터는 마지막 forecast validation window가 시작하는 위치로 설정하며, 외부 테스트를 위해 별도 데이터가 없다는 가정 하에, 기본적으로는 `ForecastHorizon`과 동일한 값이 자동으로 보여집니다.\n", "이 예제에서는 별도 테스트 셋을 준비했기 때문에, 테스트 셋으로 준비된 샘플 수 만큼의 값을 늘려야 합니다. (위 코드 셀 참고)\n", "지금까지의 설정 값이 동일하다는 가정하에서 , 값은 336 + 744 = **1,080** 로 변경합니다.\n", "\n", "*NumberOfBacktestWindows* 파라미터는 Amazon Forecast에서 [모델의 정확도](https://docs.aws.amazon.com/forecast/latest/dg/metrics.html)를 평가하기 위해 사용하는 분리된 window의 개수입니다. 이를 통해 데이터셋의 마지막 부분의 window만 사용하는 것보다 다양한 validation 데이터셋으로 성능을 측정하기에 더욱 강력한 모델을 생성할 수 있습니다.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 8: Train a predictor manually\n", "\n", "AutoPredictor로 수행하는 것이 성능이 좋기 때문에 신규 account로 생성한 Amazon Forecast는 기본적으로 AutoPredictor만 수행할 수 있도록 변경되었습니다.\n", "기존 account를 사용하는 경우나 또는 Amazon SDK를 이용하는 경우에는 기존과 동일하게 manual한 알고리즘 선택이 가능합니다.\n", "\n", "위에서 수행한 AutoPredictor가 완료되는 것을 기다릴 필요가 없이, 다른 predictor의 학습 작업을 시작할 수 있습니다. 이번에는 Python SDK인 boto3를 이용하여, Predictor를 수행하도록 합니다. \n", "\n", "학습을 시작한 다음, 다시 \"Predcitors\" 화면으로 돌아와서 2개 학습 중인 predictors의 상태를 확인합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "datagroup_arn = forecast.list_dataset_groups()['DatasetGroups'][0]['DatasetGroupArn']\n", "datagroup_arn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "forecast.create_predictor(\n", " PredictorName='bikeshare_npts_predictor',\n", " AlgorithmArn='arn:aws:forecast:::algorithm/NPTS',\n", " ForecastHorizon=336,\n", "# PerformAutoML=False,\n", "# PerformHPO=False,\n", " EvaluationParameters={\n", " 'NumberOfBacktestWindows': 1,\n", " 'BackTestWindowOffset': 1080\n", " },\n", " InputDataConfig={\n", " 'DatasetGroupArn': datagroup_arn,\n", " 'SupplementaryFeatures': [{'Name': 'holiday', 'Value': 'US'}],\n", " },\n", " FeaturizationConfig={\n", " 'ForecastFrequency': 'H',\n", " 'Featurizations': [\n", " {'AttributeName': 'demand',\n", " 'FeaturizationPipeline': [\n", " {'FeaturizationMethodName': 'filling',\n", " 'FeaturizationMethodParameters': \n", " {'aggregation': 'sum',\n", " 'backfill': 'zero',\n", " 'frontfill': 'none',\n", " 'middlefill': 'zero'}}]}\n", " ]\n", " },\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 9: Create forecasts (and maybe custom predictors?)\n", "\n", "다른 모델을 fit하기를 원한다면 (예, AutoML 모델 선택 또는 ARIMA와 같은 baseline 아키텍처 중 하나를 사용), 위와 유사한 방식으로 더 많은 학습 작업을 수행하셔도 됩니다.\n", "\n", "다음 단계는 각 predictor에 대해 \"forecast\"를 생성합니다. 이 과정에서 모델을 실행하고 예측 신뢰 구간을 추출합니다.\n", "\n", "훈련이 완료될 때마다 forecast 생성을 시작할 수 있으며, Prophet의 학습이 비교적 빠르게 나기 때문에 지금 쯤 이용할 수 있습니다. predictor를 학습하고 forecast를 생성하는 것이 오래 걸릴 수 있기 때문에, 다른 SageMaker 모델로 학습을 수행하는 방법도 가능합니다. \n", "forecast를 생성하기 위해, 왼쪽 메뉴에서 \"Forecasts\"를 클릭합니다.그리고, \"Create a Forecast\" 버튼을 클릭합니다. 각 forecast 설정은 아래와 같이 합니다.\n", "\n", "> ** Create a forecast** \n", " - Forecast name: **`bikeshare_autopredictor_forecast`**, **`bikeshare_npts_forecast`**, etc\n", " - Predictor: **`Dropdown에서 선택`** (predictor 학습이 끝나지 않으면 dropdown에서 나타나지 않습니다.)\n", " - Forecast types : **`.10, .50, .90, mean`** \n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "AWS Console에서는 forecast 생성이 되자마자 목록에서 해당 항목을 선택할 수 있으며, 아래 **Forecast ARN** 를 입력해야 합니다.이 노트북에서는 **Forecast ARN** 값을 AWS SDKs를 이용하여 가져올 수 있도록 구현하였기 때문에 forecast 작업이 완료된 후 아래 cell 부터 또는 전체 cell을 재실행(Run All)하면 결과값을 확인할 수 있습니다.\n", "\n", "**주의 사항은 forecast ARN과 predictor ARN은 서로 다릅니다.!** 왼쪽 메뉴에 \"Forecasts\"에서 forecasts를 생성한 목록에 접근할 수 있습니다.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

AWS 콘솔에서 생성한 Forecast 이름을 아래 입력합니다.

" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bikeshare_npts_forecast = \"bikeshare_npts_forecast\"\n", "# bikeshare_autopredictor_forecast = \"bikeshare_autopredictor_forecast\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "forecast= boto3.client('forecast')\n", "forecast_result = forecast.list_forecasts(\n", " Filters=[\n", " {\n", " 'Key': 'Status',\n", " 'Value': 'ACTIVE',\n", " 'Condition': 'IS'\n", " },\n", " ]\n", ")\n", "\n", "try:\n", " for f_result in forecast_result['Forecasts']:\n", " if f_result['ForecastName'] ==bikeshare_npts_forecast:\n", " bikeshare_npts_forecast = f_result['ForecastArn']\n", " print('bikeshare_npts_forecast Status : ACTIVE')\n", "except:\n", " print('bikeshare_npts_forecast Status : CREATE_IN_PROGRESS or Nothing')\n", "try:\n", " for f_result in forecast_result['Forecasts']:\n", " if f_result['ForecastName'] ==bikeshare_autopredictor_forecast:\n", " bikeshare_autopredictor_forecast = f_result['ForecastArn']\n", " print('bikeshare_autopredictor_forecast Status : ACTIVE')\n", "except:\n", " print('bikeshare_autopredictor_forecast Status : CREATE_IN_PROGRESS or Nothing')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "forecast_arns = {\n", " \"bikeshare_npts_forecast\": bikeshare_npts_forecast, # TODO ,\n", "# \"bikeshare_autopredictor_forecast\": bikeshare_autopredictor_forecast# TODO\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 10: Review model accuracy metrics\n", "\n", "*신뢰구간* 내 확률적인 forecasts 값을 생성하기 때문에, 결과를 평가하는 방식은 RMSE 점수를 비교하는 것과 같이 단순하지 않습니다. 여기서, 아래 두 개의 metrics 간의 **trade-off**가 있습니다.\n", "\n", "* accuracy : 실제 값들이 제안된 신뢰구간/확률 분포 내 존재하는지 여부 평가\n", "* precision : 제안된 신뢰 구간이 얼마나 좁아지는지 평가\n", "\n", "Predictor의 metrics는 학습 데이터셋 내 backtesting windows를 이용한 결과값이며, AWS Console에서도 직접 확인할 수 있습니다.\n", "\n", "* 왼쪽 메뉴에서 \"Predictors\"로 이동\n", "* 학습이 완료된 predictor 중 검토를 위하는 predictor 선택\n", "* scroll을 조금 내리면 \"Predictor metrics\" 에서 내용 확인\n", "\n", "아래 screenshot 예제에서 볼 수 있듯이 각각의 예측 window와 평균으로 요약한 RMSE과 10%, 50%, 90% 3가지 평가 지점에서의 평균 및 가중 quantile 손실값을 볼 수 있습니다.\n", "\n", "**` 이러한 metrics 기반으로 어떤 predictor가 가장 성능이 좋을까요? 다른 prediction windows에 따라 accuracy에서 어떤 패턴이 있습니까?`**\n", "\n", "\n", "\n", "#### Review accuracy metrics\n", "\n", "* **Weighted Quantile Loss (wQL)** 지정한 분위수 (quantile)에서 모델의 정확도를 측정합니다. 과소 예측과 과대 예측에 대해 비용 차이가 있을 때 유용합니다. \n", "\n", "* **Root Mean Square Error (RMSE)** 특이 값의 영향을 크게 하는 잔차 값의 제곱 값을 사용하는데, 몇 개의 큰 오류 예측에 대해 비용이 많이 들 수 있는 사용 사례에 대해 유용합니다.\n", "\n", "* **Weighted Absolute Percentage Error (WAPE)** 제곱 오차 대신 절대 오차를 사용하기에 RMSE 보다 특이치에 대해 더 robust한 메트릭입니다.\n", "\n", "* **Mean Absolute Percentage Error (MAPE)** 값들이 시점 간에 특이하게 다르고, 특이치가 유의미한 영향을 미치는 경우에 유용합니다.\n", "\n", "* **Mean Absolute Scaled Error (MASE)** 데이터 특성이 순환적 (cyclical)이거나, 계절적인 속성 (seasonal)을 가진 데이터 셋에 적합합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 11: Visualise and evaluate forecast quality\n", "\n", "왼쪽 메뉴에 있는 \"Forecast Lookup\"에서 forecast의 결과를 직접 확인할 수 있습니다.\n", "\n", "이 노트북에서는 Forecast Query API를 이용하여 프로그래밍 방식으로 결과를 다운로드받은 후 그래프로 시각화해서 보여 줍니다. 다양한 시각화 방식이나 custom 평가 metrics를 이용하여 구성할 수 있습니다.\n", "\n", "모델 학습을 위해 원본 소스 데이터의 timestamps를 이해했지만, inference 에서 더욱 엄격한 요구사항을 갖는다는 점에서 적합한 ISO 형식으로 시작과 끝의 timestamps를 생성할 필요가 있습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "first_test_ts = target_test_df[\"timestamp\"].iloc[0]\n", "\n", "# Remember we predict to 2 weeks horizon\n", "# [Python 3.6 doesn't have fromisoformat()]\n", "test_end_dt = datetime(\n", " int(first_test_ts[0:4]),\n", " int(first_test_ts[5:7]),\n", " int(first_test_ts[8:10]),\n", " int(first_test_ts[11:13]),\n", " int(first_test_ts[14:16]),\n", " int(first_test_ts[17:])\n", ") + timedelta(days=13, hours=-1)\n", "\n", "# Forecast wants a slightly different timestamp format to the dataset:\n", "fcst_start_date = first_test_ts.replace(\" \", \"T\")\n", "fcst_end_date = test_end_dt.isoformat()\n", "print(f\"Forecasting\\nFrom: {fcst_start_date}\\nTo: {fcst_end_date}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "forecasts = {\n", " predictor_name: {\n", " \"forecast_arn\": forecast_arn,\n", " \"forecasts\": {\n", " item_id: forecast_query.query_forecast(\n", " ForecastArn=forecast_arn,\n", " StartDate=fcst_start_date,\n", " EndDate=fcst_end_date,\n", " Filters={ \"item_id\": item_id }\n", " )\n", " for item_id in item_ids }\n", " }\n", "for (predictor_name, forecast_arn) in forecast_arns.items() if forecast_arn != ''}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Amazon Forecast와 다양한 SageMakers 모델들이 다양한 형식으로 결과를 출력하기 때문에, 이를 비교하기 위한 목적으로 **결과를 표준화**하여 local CSV 파일로 저장합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "clean_results_df = pd.DataFrame()\n", "for predictor_name, predictor_data in forecasts.items():\n", " for item_id, forecast_data in predictor_data[\"forecasts\"].items():\n", " predictions = forecast_data[\"Forecast\"][\"Predictions\"]\n", " pred_mean_df = pd.DataFrame(predictions[\"mean\"])\n", " pred_timestamps = pd.to_datetime(pred_mean_df[\"Timestamp\"].apply(lambda s: s.replace(\"T\", \" \")))\n", " \n", " df = pd.DataFrame()\n", " df[\"timestamp\"] = pred_timestamps\n", " df[\"model\"] = f\"amzforecast-{predictor_name}\"\n", " df[\"customer_type\"] = item_id\n", " df[\"mean\"] = pred_mean_df[\"Value\"]\n", " df[\"p10\"] = pd.DataFrame(predictions[\"p10\"])[\"Value\"]\n", " df[\"p50\"] = pd.DataFrame(predictions[\"p50\"])[\"Value\"]\n", " df[\"p90\"] = pd.DataFrame(predictions[\"p90\"])[\"Value\"]\n", " \n", " clean_results_df = clean_results_df.append(df)\n", "\n", "!mkdir -p results/amzforecast\n", "clean_results_df.to_csv(\n", " f\"./results/amzforecast/results_clean.csv\",\n", " index=False\n", ")\n", "s3.upload_file(\n", " Filename=f\"./results/amzforecast/results_clean.csv\",\n", " Bucket=bucket,\n", " Key=f\"amzforecast/results/results_clean.csv\"\n", ")\n", "print(\"Clean results saved to ./results/amzforecast/results_clean.csv\")\n", "clean_results_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "최종적으로 표준화된 형식을 사용하여 결과를 시각화합니다.\n", "\n", "(노트북에서 단순화된 시각화를 위해서 util 폴더 아래 플로팅 기능을 사용합니다.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First, prepare the actual data (training + test) for easy plotting:\n", "first_plot_dt = test_end_dt - timedelta(days=21)\n", "actuals_df = target_train_df.append(target_test_df)\n", "actuals_df[\"timestamp\"] = pd.to_datetime(actuals_df[\"timestamp\"])\n", "actuals_plot_df = actuals_df[\n", " (actuals_df[\"timestamp\"] >= first_plot_dt)\n", " & (actuals_df[\"timestamp\"] <= test_end_dt)\n", "]\n", "actuals_plot_df.rename(columns={ \"item_id\": \"customer_type\"}, inplace=True)\n", "\n", "util.plot_fcst_results(actuals_plot_df, clean_results_df)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "통계적인 시계열 예측을 AWS Console에서 수행하였고, 소스코드로 다운로드 받고 처리를 하였습니다. 과학적인 지식이 요구되지 않았으나, 심층적인 작업을 원하는 경우에는 모델의 아키텍처와 하이퍼파라미터를 조정하여 수행해볼 수 있습니다. \n", "\n", "**`또한, 정해진 워크샵 시간 내 완료하지 못한 AutoPredictor와 결과를 꼭 비교해보시기 바랍니다.`**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extension exercises and exploring further\n", "\n", "[Amazon Forecast metrics docs](https://docs.aws.amazon.com/forecast/latest/dg/metrics.html)에서 언급된 수식과 위 시각화를 위한 소스코드 예제를 이용하여 이번 예측 window에 대한 RMSE와 가중 quantile 손실 값을 계산할 수 있습니까? 학습에서 계산한 Amazon Forecast 값과 어떻게 비교를 할까요?\n", "\n", "Amazon Forecast에는 내부에서 제공하는 시간 관련 featurization을 가지고 있기 때문에 정확한 절대 timestamp와 날짜가 중요합니다. related timeseries 특성인 `workingday` 제거하는 것은 예측 품질에 많은 영향을 줄까요? `holiday`의 경우는 어떨까요? 추가적으로 전체 데이터셋의 timestamps를 날짜로 변경하면 어떻게 될까요?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Thanks for joining in! (Clean-up time)\n", "\n", "[Amazon Forecast 가격](https://aws.amazon.com/forecast/pricing/) 은 다음과 같습니다.\n", "\n", "* 생성된 forecasts\n", "* 데이터 저장\n", "* 학습 시간\n", "\n", "따라서, 일부 서비스처럼 삭제에 대해 걱정할 실시간 endpoint 컴퓨팅 리소스는 없습니다. 하지만 데이터 스토리지 비용이 중요한 경우에는 이를 정리할 필요는 있습니다.\n", "\n", "Amazon Forecast console을 통해 모든 리소스 (forecasts, predictors, import jobs, 데이터셋과 데이터셋 그룹 등) 를 삭제할 수 있습니다. S3 버킷과 SageMaker로 수행하였다면 notebook instance 를 stop해야 합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_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.8.12" } }, "nbformat": 4, "nbformat_minor": 4 }