{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting (AAAI'21 Best Paper)\n", "--------------\n", "이 실습은 Informer의 original github 코드를 기반으로 SageMaker에서 학습하는 방법을 가이드하고자 만들었습니다. 모든 라이선스는 [여기](https://github.com/zhouhaoyi/Informer2020) 구현된 원본 소스코드의 라이선스 정책을 따르고 있으며, [Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting](https://arxiv.org/abs/2012.07436) 논문에서 자세한 설명을 확인할 수 있습니다.\n", "\n", "

\n", "

\"\"
\n", "

\n", "Figure 1. The architecture of Informer.\n", "

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 필요한 패키지 설치 및 업데이트" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "install_needed = True # should only be True once\n", "# install_needed = False" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "import sys\n", "import IPython\n", "\n", "if install_needed:\n", " print(\"installing deps and restarting kernel\")\n", " !{sys.executable} -m pip install -U 'sagemaker[local]'\n", " !{sys.executable} -m pip install -U sagemaker-experiments smdebug\n", " !{sys.executable} -m pip install -U sagemaker\n", " !/bin/bash ./local/local_change_setting.sh\n", " IPython.Application.instance().kernel.do_shutdown(True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 환경 설정\n", "\n", "Sagemaker 학습에 필요한 기본적인 package를 import 합니다.
\n", "[boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)는 AWS 리소스와 동작하는 python 클래스를 제공하며, HTTP API 호출을 숨기는 추상화 모델입니다. boto3를 통해 python에서 Amazon EC2 인스턴스, S3 버켓과 같은 AWS 리소스와 동작할 수 있습니다.
\n", "[sagemaker python sdk](https://sagemaker.readthedocs.io/en/stable/)는 Amazon SageMaker에서 기계 학습 모델을 교육 및 배포하기 위한 오픈 소스 라이브러리입니다.
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import sagemaker\n", "# import splitfolders\n", "\n", "import os\n", "import time\n", "import warnings\n", "\n", "from smexperiments.experiment import Experiment\n", "from smexperiments.trial import Trial\n", "\n", "import boto3\n", "import numpy as np\n", "\n", "# from tqdm import tqdm\n", "from time import strftime\n", "\n", "from sagemaker import get_execution_role\n", "from sagemaker.pytorch import PyTorch\n", "\n", "warnings.filterwarnings('ignore')\n", "%config InlineBackend.figure_format = 'retina'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "role = get_execution_role()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sagemaker.__version__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Dataset 준비" ] }, { "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", "assert data_prefix, \"Variable `data_prefix` missing from IPython store\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "train_df = pd.read_csv('./data/amzforecast/target_train.csv')\n", "test_df = pd.read_csv('./data/amzforecast/target_test.csv')\n", "related_df = pd.read_csv('./data/amzforecast/related.csv')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data_df = pd.concat([train_df,test_df])\n", "\n", "data = pd.merge(data_df, related_df, on=['timestamp', 'item_id'], how='left')\n", "data['item_id'] = data['item_id'].astype('category')\n", "data['item_id'] = data['item_id'].values.codes\n", "data.rename(columns = {'timestamp':'date'},inplace=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "col_list = data.columns.to_list()\n", "new_col_list = col_list[:2] + col_list[3:] + [col_list[2]]\n", "data = data[new_col_list]\n", "data.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "session = boto3.Session() \n", "s3 = session.client(service_name=\"s3\")\n", "\n", "data_filename = 'informer_dataset.csv'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Writing dataframes to file...\")\n", "!mkdir -p ./data/informer\n", "\n", "data.to_csv(\n", " f\"./data/informer/{data_filename}\",\n", " index=False\n", ")\n", "\n", "print(\"Uploading dataframes to S3...\")\n", "s3.upload_file(\n", " Filename=f\"./data/informer/{data_filename}\",\n", " Bucket=bucket,\n", " Key=f\"{data_prefix}informer/{data_filename}\"\n", ")\n", "dataset_path = f's3://{bucket}/{data_prefix}informer'\n", "print(dataset_path)\n", "\n", "print(\"Done\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Experiments 관리\n", "\n", "Amazon SageMaker에는 실험을 관리할 수 있는 [SageMaker Experiments](https://aws.amazon.com/ko/blogs/aws/amazon-sagemaker-experiments-organize-track-and-compare-your-machine-learning-trainings/) 서비스가 있습니다. 반복적인 실험에 대해 로깅을 남기기 위한 실험 이름 (create_experiment)과 trial (create_trial) 이름을 설정하는 함수입니다.
이러한 메타 정보를 이용하여 향후 ML의 실험 관리가 용이해 질 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_experiment(experiment_name):\n", " try:\n", " sm_experiment = Experiment.load(experiment_name)\n", " except:\n", " sm_experiment = Experiment.create(experiment_name=experiment_name,\n", " tags=[{'Key': 'modelname', 'Value': 'informer'}])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_trial(experiment_name, i_type, i_cnt, spot=False):\n", " create_date = strftime(\"%m%d-%H%M%s\")\n", " algo = 'informer'\n", " \n", " spot = 's' if spot else 'd'\n", " if i_type=='local' or i_type=='local_gpu':\n", " i_type = 'local'\n", " else:\n", " i_type = i_type[3:9].replace('.','-')\n", " \n", " trial = \"-\".join([i_type,str(i_cnt),algo, spot])\n", " \n", " sm_trial = Trial.create(trial_name=f'{experiment_name}-{trial}-{create_date}',\n", " experiment_name=experiment_name)\n", "\n", " job_name = f'{sm_trial.trial_name}'\n", " return job_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. 실험 설정\n", "\n", "학습 시 사용한 소스코드와 output 정보를 저장할 위치를 선정합니다. 이 값은 필수로 설정하지 않아도 되지만, 코드와 결과물을 S3에 저장할 때 체계적으로 정리하는데 활용할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "code_location = f's3://{bucket}/poc_informer/sm_codes'\n", "output_path = f's3://{bucket}/poc_informer/output' " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "실험에서 표준 출력으로 보여지는 metrics 값을 정규 표현식을 이용하여 SageMaker에서 값을 capture할 수 있습니다. 이 값은 필수로 설정하지 않아도 되지만, SageMaker Experiments에 Metrics 정보를 남길 수 있어서 실험 관리에 유용합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "metric_definitions = [\n", " {'Name': 'Epoch', 'Regex': 'Epoch: ([-+]?[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?),'},\n", " {'Name': 'train_loss', 'Regex': 'Train Loss: ([-+]?[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?),'},\n", " {'Name': 'valid_loss', 'Regex': 'Valid Loss: ([-+]?[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?),'},\n", " {'Name': 'test_loss', 'Regex': 'Test Loss: ([-+]?[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?),'},\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다양한 실험 조건을 테스트하기 위해 hyperparameters로 argument 값들을 노트북에서 설정할 수 있으며, 이 값은 학습 스크립트에서 argument인 변수로 받아서 활용이 가능합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hyperparameters = {\n", " 'model' : 'informer', # model of experiment, options: [informer, informerstack, informerlight(TBD)]\n", " 'data' : 'bikeshare', # data\n", " 'root_path' : '/', # root path of data file\n", " 'data_path' : data_filename, # data file\n", " 'features' : 'M', # forecasting task, options:[M, S, MS]; M:multivariate predict multivariate, S:univariate predict univariate, MS:multivariate predict univariate\n", " 'target' : 'demand', # target feature in S or MS task\n", " 'freq' : 'h', # freq for time features encoding, options:[s:secondly, t:minutely, h:hourly, d:daily, b:business days, w:weekly, m:monthly], you can also use more detailed freq like 15min or 3h\n", " 'checkpoints' : 'informer_checkpoints', # location of model checkpoints\n", "\n", " 'seq_len' : 720, # input sequence length of Informer encoder\n", " 'label_len' : 336, # start token length of Informer decoder\n", " 'pred_len' : 336, # prediction sequence length\n", " # Informer decoder input: concat[start token series(label_len), zero padding series(pred_len)]\n", "\n", " 'factor' : 5, # probsparse attn factor\n", " 'd_model' : 512, # dimension of model\n", " 'n_heads' : 8, # num of heads\n", " 'e_layers' : 2, # num of encoder layers\n", " 'd_layers' : 1, # num of decoder layers\n", " 'd_ff' : 2048, # dimension of fcn in model\n", " 'dropout' : 0.05, # dropout\n", " 'attn' : 'prob', # attention used in encoder, options:[prob, full]\n", " 'embed' : 'timeF', # time features encoding, options:[timeF, fixed, learned]\n", " 'activation' : 'gelu', # activation\n", " 'distil' : True, # whether to use distilling in encoder\n", " 'output_attention' : False, # whether to output attention in ecoder\n", " 'mix' : True,\n", " 'padding' : 0,\n", " 'freq' : 'h',\n", " 'do_predict' : True,\n", " 'batch_size' : 32,\n", " 'learning_rate' : 0.0001,\n", " 'loss' : 'mse',\n", " 'lradj' : 'type1',\n", " 'use_amp' : False, # whether to use automatic mixed precision training\n", "\n", " 'num_workers' : 0,\n", " 'itr' : 1,\n", " 'train_epochs' : 1, ## Training epochs\n", " 'patience' : 3,\n", " 'des' : 'exp',\n", " 'use_multi_gpu' : False\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "분산학습과 spot 학습을 사용할지를 선정할 수 있습니다.
\n", "분산학습의 경우 [SageMaker data parallel library](https://docs.aws.amazon.com/sagemaker/latest/dg/data-parallel.html)를 사용하고자 할 경우 distribution을 아래와 같이 설정한 후 사용할 수 있습니다. 학습 스크립트는 분산 학습 Library로 구현이 필요합니다.
\n", "[spot 학습](https://docs.aws.amazon.com/sagemaker/latest/dg/model-managed-spot-training.html)을 사용하고자 할 경우 학습 파라미터에 spot 파라미터를 True로 변경한 다음, 자원이 없을 때 대기하는 시간인 max_wait (초)를 설정해야 합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment_name = 'informer-poc-exp1'\n", "distribution = None\n", "do_spot_training = True\n", "max_wait = None\n", "max_run = 4*60*60" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "instance_type=\"ml.g5.xlarge\"\n", "# instance_type='local_gpu'\n", "instance_count=1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. 분산 설정, checkpoints 설정, 데이터 위치 설정, Local Mode 설정" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6-1. 분산 설정" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image_uri = None\n", "train_job_name = 'sagemaker'\n", "\n", "\n", "train_job_name = 'informer-dist'\n", "distribution = {}\n", "\n", "if hyperparameters.get('use_multi_gpu') and instance_type in ['ml.p3.16xlarge', 'ml.p3dn.24xlarge', 'ml.p4d.24xlarge', 'local_gpu']:\n", " distribution[\"smdistributed\"]={ \n", " \"dataparallel\": {\n", " \"enabled\": True\n", " }\n", " }\n", "else:\n", " distribution = None\n", "\n", "if do_spot_training:\n", " max_wait = max_run\n", "\n", "print(\"train_job_name : {} \\ntrain_instance_type : {} \\ntrain_instance_count : {} \\nimage_uri : {} \\ndistribution : {}\".format(train_job_name, instance_type, instance_count, image_uri, distribution)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6-2. checkpoints와 데이터 위치 설정, Local Mode 설정" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "\n", "source_dir = f'{Path.cwd()}/Informer2020'\n", " \n", "if instance_type =='local_gpu' or instance_type =='local':\n", " from sagemaker.local import LocalSession\n", " sagemaker_session = LocalSession()\n", " sagemaker_session.config = {'local': {'local_code': True}}\n", " inputs = f'file://{Path.cwd()}/data/informer'\n", "\n", " do_spot_training = False\n", " checkpoint_s3_uri=None\n", " max_wait = None\n", "else:\n", " sagemaker_session = sagemaker.Session()\n", " inputs = dataset_path\n", " checkpoint_s3_uri = f's3://{bucket}/poc_informer/checkpoints'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. 학습을 위한 Estimator 선언\n", "\n", "AWS 서비스 활용 시 role (역할) 설정은 매우 중요합니다. 이 노트북에서 사용하는 role은 노트북과 training job을 실행할 때 사용하는 role이며, role을 이용하여 다양한 AWS 서비스에 대한 접근 권한을 설정할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "create_experiment(experiment_name)\n", "job_name = create_trial(experiment_name, instance_type, instance_count, do_spot_training)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# all input configurations, parameters, and metrics specified in estimator \n", "# definition are automatically tracked\n", "estimator = PyTorch(\n", " entry_point='main_informer.py',\n", " source_dir=source_dir,\n", "# git_config=git_config,\n", " role=role,\n", " sagemaker_session=sagemaker_session,\n", " framework_version='1.10',\n", " py_version='py38',\n", " instance_count=instance_count,\n", " instance_type=instance_type,\n", "# volume_size=256,\n", " code_location = code_location,\n", " output_path=output_path,\n", " hyperparameters=hyperparameters,\n", " distribution=distribution,\n", " metric_definitions=metric_definitions,\n", " max_run=max_run,\n", " checkpoint_s3_uri=checkpoint_s3_uri,\n", " use_spot_instances=do_spot_training,\n", " max_wait=max_wait,\n", " disable_profiler=True,\n", " debugger_hook_config=False,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. 학습 수행 - 시작" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "# Now associate the estimator with the Experiment and Trial\n", "estimator.fit(\n", " inputs={'training': inputs}, \n", " job_name=job_name,\n", " experiment_config={\n", " 'TrialName': job_name,\n", " 'TrialComponentDisplayName': job_name,\n", " },\n", " wait=False\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "job_name=estimator.latest_training_job.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "아래 명령어를 이용하여 시작된 학습에 대한 로그를 노트북에서 확인합니다. 이 로그는 CloudWatch에서도 확인이 가능합니다.
\n", "아래 명령어를 실행해도 학습이 시작되는 것이 아니며, 실행된 training job의 로그만 보는 것입니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sagemaker_session.logs_for_job(job_name=job_name, wait=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9. 학습 결과 확인\n", "\n", "학습이 완료된 다음 S3에 저장된 산출물을 확인합니다.
model 결과물은 model.tar.gz에 저장되어 있고, 이외 학습 중 로그, 결과 산출물 등은 output.tar.gz에 저장할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "artifacts_dir = estimator.model_data.replace('model.tar.gz', '')\n", "print(artifacts_dir)\n", "!aws s3 ls --human-readable {artifacts_dir}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
S3에 저장된 학습 결과 산출물을 모두 노트북에 다운로드 받은 다음, 압축을 풉니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_dir = './model'\n", "\n", "!rm -rf $model_dir\n", "\n", "import json , os\n", "\n", "if not os.path.exists(model_dir):\n", " os.makedirs(model_dir)\n", "\n", "!aws s3 cp {artifacts_dir}model.tar.gz {model_dir}/model.tar.gz\n", "!tar -xvzf {model_dir}/model.tar.gz -C {model_dir}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 10. 학습 결과의 Visualization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "학습 스크립트에는 마지막 단계에 최종 학습된 모델을 이용하여 predict를 실행한 결과를 real_prediction.npy에 저장한 후 output.tar.gz로 압축하여 S3에 업로드 합니다. 이 결과를 다시 노트북에서 load한 후 plot하여 보여줍니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "setting = 'informer_bikeshare_ftM_sl720_ll336_pl336_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtFalse_mxFalse_exp_0'\n", "\n", "# the prediction will be saved in ./results/{setting}/real_prediction.npy\n", "prediction = np.load(f'./model/results/{setting}/real_prediction.npy')\n", "prediction.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.figure()\n", "plt.plot(prediction[0,:,-1])\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "학습 스크립트에는 마지막 단계에 최종 학습된 모델을 이용하여 테스트 데이터에 대한 추론 결과를 pred.npy에, 실제 결과는 true.npy에 저장한 후 output.tar.gz로 압축하여 S3에 업로드 합니다. 이 결과를 다시 노트북에서 load한 후 plot하여 보여줍니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# When we finished exp.train(setting) and exp.test(setting), we will get a trained model and the results of test experiment\n", "# The results of test experiment will be saved in ./results/{setting}/pred.npy (prediction of test dataset) and ./results/{setting}/true.npy (groundtruth of test dataset)\n", "\n", "preds = np.load(f'./model/results/{setting}/pred.npy')\n", "trues = np.load(f'./model/results/{setting}/true.npy')\n", "\n", "# [samples, pred_len, dimensions]\n", "preds.shape, trues.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import seaborn as sns" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# draw OT (Oil Temperature) prediction\n", "plt.figure()\n", "plt.plot(trues[0,:,-1], label='GroundTruth')\n", "plt.plot(preds[0,:,-1], label='Prediction')\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def cal_metrics(pred, target):\n", " return {\n", " 'MSE': ((pred - target) ** 2).mean(),\n", " 'MAE': np.abs(pred - target).mean()\n", " }" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cal_metrics(preds, trues)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "instance_type": "ml.c5.large", "kernelspec": { "display_name": "conda_pytorch_p38", "language": "python", "name": "conda_pytorch_p38" }, "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 }