boto3を使ってAmazon Forecastの時系列予測をする流れ

#Amazon Forecastとは
Amazon Forecastをご存知でしょうか？簡単に説明すると、時系列データをアップロードすると自動で時系列予測モデルを生成し、予測結果を出力してくれるフルマネージドサービスです。データが手元にある状態であれば誰でも予測ができる画期的なサービスです。
以前とあるAWSのセミナーに参加した時、以下の言葉がとても印象的でした。
>「モデルについて考える時代はもう終わり、これからはデータを取得する一連のシステム構築にのみ専念すれば良い」

なぜモデルについて考える必要は無くなったのでしょうか？その答えの一つに「**AutoML**」という機能があるからです。

2021/8/30時点で、Amazon Forecastには以下の6つの時系列予測アルゴリズムが用意されています。

【Neural Networks】
・CNN-QR
・DeepAR+
【Flexible Local Algorithms】
・Prophet
・NPTS
【Baseline Algorithms】
・NPTS
・ARIMA
・ETS

それぞれ固有のアルゴリズムであり、「AutoML」では、こちらで用意したデータセットに対して６つのアルゴリズムのどれが一番最適かを自動的に算出して適用してくれます。これにより、アルゴリズム選択に割く時間と手間が大幅に削減できます。体感ですが、DeepAR+が最も精度が良いなと思います。こちらについてはクラスメソッドさんが詳しく紹介しているので、そちらを参照してください。

[【Amazon Forecast】DeepARの論文が気になったのでざっくり読んでみた](https://dev.classmethod.jp/articles/amazon-forecast-deepar-paper/)

今回は、AWSで用意されているSDK for Python、要するにBoto3を使って時系列データを学習させて、予測結果をS3に保存するという一連の流れを紹介します。

#boto3を使ってForecastを動かす
##1.学習データをS3に格納
今回使用するバケットとフォルダ名を以下のようにします
Bucket:weight-bucket
Folder:learning-data

Pandasで一所懸命に作ったデータ、またはあらかじめ手に入れたデータをBucketにcsv形式でアップロードします。
学習データは大きく3つに分かれており、今回は二人の体重の時系列予測を行う想定で３つのデータセットを使います。

Target time series data : 予測したい値（体重）
Item metadata data : その人固有のパラメータ（身長、通勤形態）
Related time series data : 予測に関連するデータ（摂取カロリー）

必ず3種類のデータが必要ではなく、Target time series dataさえあれば予測できます。ただし、精度は下がりますが、、
・Target time series data

|timestamp|target_value|item_id|
|:---:|:---:|:---:|
|2021-08-01 00:00:00|55.12|sadako_weight|
|2021-08-01 00:30:00|55.00|sadako_weight|
|2021-08-01 01:00:00|55.08|sadako_weight|
|・・・|・・・|・・・|
|2021-08-30 23:30:00|52.12|sadako_weight|
|2021-08-01 00:00:00|57.12|yoshiki_weight|
|2021-08-01 00:30:00|57.00|yoshiki_weight|
|2021-08-01 01:00:00|56.98|yoshiki_weight|
|・・・|・・・|・・・|
|2021-08-30 23:30:00|62.12|yoshiki_weight|

上の表のように、Amazon Forecastにuploadするデータは決まった形式があります。
timestampは「2021/08/01 00:00:00」と言うスラッシュ区切りには対応していないという記事を見たので、replace('/','-')の処理を入れてあげてください。target_valueは、予測したいデータ（今回は30minメッシュの人の体重）を入れます。item_idというのは、各人間の固有idです。item_idをつけることで、多くの人の体重予測を行うことができます。

・Related time series data

|item_id|timestamp|meal_amount(kcal)|
|:---:|:---:|:---:|
|sadako_weight|2021-08-01 00:00:00|10.00|
|sadako_weight|2021-08-01 00:30:00|0.00|
|sadako_weight|2021-08-01 01:00:00|0.00|
|・・・|・・・|・・・|
|sadako_weight|2021-08-30 23:30:00|0.00|
|yoshiki_weight|2021-08-01 00:00:00|0.00|
|yoshiki_weight|2021-08-01 00:30:00|0.00|
|yoshiki_weight|2021-08-01 01:00:00|0.00|
|・・・|・・・|・・・|
|yoshiki_weight|2021-08-30 23:30:00|500.00|

こちらもデータ形式を合わせます。今回は、食べたご飯やお菓子のカロリーを30minメッシュで学習データとして使います。

・Item metadata data

|item_id|height|commute_method|
|:---:|:---:|:---:|
|sadako_weight|160|walk|
|yoshiki_weight|170|bicycle|

これらのデータをBucket::weight-bucket/learning-dataに
target_data.csv、relational_data.csv、metadata_data.csvとしてそれぞれ格納しましょう。

boto3で格納する場合、以下になります。

```py
s3 = boto3.resource(
    's3',
    region_name='ap-northeast-1',
    aws_access_key_id = '任意',
    aws_secret_access_key = '任意',
)
df = #対応するDataFrame
obj = s3.Object('weight-bucket',f'learning-data/target_data.csv')
r = obj.put(Body = df.to_csv(index = False))
```

##2.データセットグループ作成
次に、データセットグループを作成します。要するに、今回の体重予測プロジェクトをAmazon Forecastに作成するというイメージです。そのプロジェクトの中にデータをアップロードします。

```py
#Forecastを動かすためのオプジェクト
forecast_client = boto3.client(
    'forecast',
    region_name='ap-northeast-1',
    aws_access_key_id = '任意',
    aws_secret_access_key = '任意',
)
#データセットグループ作成
create_dataset_group_response = forecast_client.create_dataset_group(
    DatasetGroupName='weight',
    Domain='CUSTOM',
)
# 作成したデータセットグループのArnを保持
datasetGroupArn = create_dataset_group_response['DatasetGroupArn']
```

Domainというのは、Forecast側であらかじめ用意しているデータセット形式です。例えば、小売の需要予測であれば'RETAIL'をDomainに設定するとデータセットがRETAIL用の形式になります。今回の体重予測はドメインが用意されていないので'CUSTOM'を選択します。

##3.各学習データセットの形式を指定
３種類の学習データの形式それぞれをデータセットに登録します。

```py
#例、Target time series dataの形式のみ抜粋
create_dataset_response = forecast_client.create_dataset(
    DatasetName='weight_dataset',
    Domain='CUSTOM',
    DatasetType='TARGET_TIME_SERIES', #or 'RELATED_TIME_SERIES' or'ITEM_METADATA'
    DataFrequency='30min',
    Schema={
        'Attributes': [
            {
                'AttributeName': 'timestamp',
                'AttributeType': 'timestamp'
            },
            {
                'AttributeName': 'target_value',
                'AttributeType': 'float'
            },
            {
                'AttributeName': 'item_id',
                'AttributeType': 'string'
            }
        ]
    }
)
# 作成したデータセットのArnを保持
datasetArn = create_dataset_response['DatasetArn']

# データセットグループにデータセットを登録
forecast_client.update_dataset_group(DatasetGroupArn=datasetGroupArn, DatasetArns=[datasetArn])
```

##4.各学習データをForecastにアップロード
S3に保存していた学習データをデータセットにアップロードします。

```py
#学習データをアップロード
response = forecast_client.create_dataset_import_job(
    DatasetImportJobName='weight_dataset_import',
    DatasetArn='arn:aws:forecast:ap-northeast-1:"アカウントID":dataset/weight_dataset',
    DataSource={
        'S3Config': {
            'Path': f's3://weight-bucket/learning-data/target_data.csv',
            'RoleArn': 'arn:aws:iam::"アカウントID":role/service-role/AmazonForecast-ExecutionRole-"任意"',
        }
    },
    TimestampFormat='yyyy-MM-dd HH:mm:ss'
)
```
RoleArnはForecastコンソール内を探せば見つかると思います、多分。

##5.予測モデルを作成
予測モデルを作成するために、予測子 Predictorを作成します。今回はAutoMLを使い、一週間先までの30minメッシュ予測モデルを作成します。

```py
#予測モデル作成
response = forecast_client.create_predictor(
    PredictorName='weight_dataset_predictor',
    ForecastHorizon=336,
    PerformAutoML=True,
    InputDataConfig={
        'DatasetGroupArn': 'arn:aws:forecast:ap-northeast-1:"アカウントID":dataset-group/weight',
    },
    FeaturizationConfig={
        'ForecastFrequency': '30min'
    }
)
```

##6.予測実行
作成した予測子を使って、一週間先までの体重推移の予測を開始です！

```py
#予測実行
response = forecast_client.create_forecast(
    ForecastName='weight_dataset_forecast',
    PredictorArn='arn:aws:forecast:ap-northeast-1:"アカウントID":predictor/weight_dataset_predictor',
    ForecastTypes=['0.1', '0.3', '0.5', '0.7', '0.9']
)
```
このForecastTypesというのは、実際の体重の値が予測結果の値に収まる確率を表しています。
例えば、0.5は50％の確率で予測結果が実際の体重の値に収まるだろうという指標です。0.5は予測結果の平均値だと思っていいのではないでしょうか。0.9を使いたい時というのは、今週は飲み歩く予定だから、ちょっと多めに体重予測の結果を見積もろうか、、、という時ですかね。？

##7.予測結果をS3に格納
Bucket：weight-bucketに'/forecast_data'を作成し、そこに格納してあげます。

```py
#S3に結果を格納
response = forecast_client.create_forecast_export_job(
    ForecastExportJobName='weight_forecast_export',
    ForecastArn='arn:aws:forecast:ap-northeast-1:"アカウントID":forecast/weight_dataset_forecast',
    Destination={
        'S3Config': {
            'Path': 's3://weight-bucket/forecast_data',
            'RoleArn': 'arn:aws:iam::"アカウントID":role/service-role/AmazonForecast-ExecutionRole-"任意"'
        }
    }
)
```

##8.予測結果を可視化
S3にデータを格納していますが、ForecastArnを指定することでも予測結果を可視化することができます。

```py
forecast_query_client = boto3.client(
    'forecastquery',
    region_name='ap-northeast-1',
    aws_access_key_id = '任意',
    aws_secret_access_key = '任意',
)
#予測結果をいじる
id_list = ["sadako_weight","yoshiki_weight"]
for id_value in id_list:
    response = forecast_query_client.query_forecast(
        ForecastArn='arn:aws:forecast:ap-northeast-1:"アカウントID":forecast/weight_dataset_forecast',
        Filters={
            'item_id': id_value
        }
    )
    forecast_dict = response['Forecast']['Predictions']['p50']
    forecast_df = pd.DataFrame({'Datetime': [],
                       'Forecast_Weight(kwh)': []})
    for raw in forecast_dict:
        forecast_df = forecast_df.append({'Datetime':dt.datetime.strptime(raw['Timestamp'].replace("T"," "), '%Y-%m-%d %H:%M:%S'), 'Forecast_Weight(kwh)':raw['Value']}, ignore_index=True)
    forecast_df = forecast_df.set_index('Datetime')
    forecast_df.plot()
```

#Lambdaによる自動化への応用
上記のSDKを使うことにより、Lambdaで予測を自動化することができます。
ぜひ試してみてください。
次は、SAMを使ってLambdaをデプロイする記事を書こうかなと思います。