AWS Lambdaでkintoneアプリの簡易自動バックアップを作ってみた その1(フォーム設計情報とレコードの取得)

kintone自体はアプリケーションを非常に簡単に作成できるのですが、そのバックアップ、となると有償の製品を購入するか、あるいは手動で実施する、というのが現状です。

そこで、有償のバックアップとまでは行かなくても、簡易的なバックアップの仕組みを作れないか、試してみました。

ゴール

とりあえず以下の条件をゴールとします。

  1. バックアップ対象は「フォームの設計」と「データ一式」
  2. バックアップは自動で定期的に実施される
  3. 必要であればリストアの仕組みも作る

バックアップのための仕組み

・フォームの設計情報をバックアップ
 →フォーム設計情報取得
・データ一式のバックアップ
 →第10回 kintone REST APIを利用したレコード取得

これらをAWS Lambda(Python 3)上から呼び出せるように実装します。リストアもjson形式で対応できそうなので、上記のページの通りの結果が得られればバックアップとしては問題なさそうです。

処理対象

こんな感じの備品在庫管理(既成品)を利用します。

スクリーンショット 2018-01-28 16.28.42.png

実装

前処理

色々と準備が必要なので、最初に実施します。

KINTONE_BASE_URL = os.environ['KINTONE_URL']
KINTONE_FORM_BASE_URL = os.environ['KINTONE_FORM_BASE_URL']
URL = KINTONE_BASE_URL.format(
    kintone_domain=os.environ['KINTONE_DOMAIN'],
    kintone_app=os.environ['KINTONE_APP']
)
FORM_URL = KINTONE_FORM_BASE_URL.format(
    kintone_domain=os.environ['KINTONE_DOMAIN'],
    kintone_app=os.environ['KINTONE_APP']
)
HEADERS_KEY = os.environ['KINTONE_HEADERS_KEY']
API_KEY = os.environ['KINTONE_API_KEY']

S3_BUCKET = os.environ['S3_BUCKET']
S3_OBJECT_PREFIX = os.environ['S3_OBJECT_PREFIX']

s3_client = boto3.client('s3')

全レコードの取得

こんな感じでQueryに何も指定しない全件取得を行います。

def get_all_records(headers):
    query = u''

    response_record = requests.get(URL + query , headers=headers)
    return json.loads(response_record.text)

フォーム設計情報取得

フォームは前述のページを参考にこのような実装になります。

def get_form_info(headers):

    response_record = requests.get(FORM_URL, headers=headers)
    return json.loads(response_record.text)

取得したデータをS3にバックアップとして保持

botoライブラリを利用して、tmpフォルダに一時的に作成したファイルをS3にアップロードします。

def put_data_to_s3(contents):
    date = datetime.now()
    date_str = date.strftime("%Y%m%d%H%M%S")
    tmp_dir = "/tmp/"
    tmp_file = S3_OBJECT_PREFIX + "_" + date_str + ".json"

    with open(tmp_dir + tmp_file, 'w') as file:
        file.write(json.dumps(contents, ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ': ')))

    s3_client.upload_file(tmp_dir + tmp_file, S3_BUCKET, tmp_file)

    return True

上記を呼び出し側メソッド

今まで作成したきた部品をつなぎ合わせます。それぞれレコード一覧とフォーム設計情報を一つのJSONファイルにまとめる様に実装しています。

def run(event, context):

    headers = {HEADERS_KEY: API_KEY}
    record_data = get_all_records(headers)
    records = record_data['records']

    form_data =get_form_info(headers)
    forms = form_data['properties']

    result = {
        "records": records,
        "properties": forms
    }

    return put_data_to_s3(result)

(おまけ)環境変数はステージごとに切り替えるように実装

クラメソさんの記事を参考に、ステージごとに環境変数を切り替えられるようにしています。

serverless.yml
service: aws-kintone-backup

provider:
  name: aws
  runtime: python3.6
  region: us-east-1
  stage: ${opt:stage, self:custom.defaultStage}
  environment:
    KINTONE_URL: https://{kintone_domain}/k/v1/records.json?app={kintone_app}
    KINTONE_FORM_BASE_URL: https://{kintone_domain}/k/v1/form.json?app={kintone_app}
    KINTONE_HEADERS_KEY: X-Cybozu-API-Token
custom:
  defaultStage: dev
  otherfile:
    environment:
      dev: ${file(./conf/dev.yml)}
      prd: ${file(./conf/prd.yml)}

functions:
  run:
    handler: handler.run
    environment:
      KINTONE_DOMAIN: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_DOMAIN}
      KINTONE_API_KEY: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_API_KEY}
      KINTONE_APP: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_APP}
      S3_BUCKET: ${self:custom.otherfile.environment.${self:provider.stage}.S3_BUCKET}
      S3_OBJECT_PREFIX: ${self:custom.otherfile.environment.${self:provider.stage}.S3_OBJECT_PREFIX}
dev.yml
KINTONE_DOMAIN: xxxxxx.cybozu.com
KINTONE_API_KEY: XXXXXXXXX
KINTONE_APP: XXX
S3_BUCKET: XXXXXX
S3_OBJECT_PREFIX: XXXXXX

slsコマンドでアップロードをする際に--stageオプションを指定する必要があります。(デフォルトはdev

実行してみる

さて、それぞれ環境変数を指定して実行してみます。無事S3にファイルが出力されました。中身も無事出力されています。(コンテンツが長いからここには載せないけど)

スクリーンショット 2018-02-05 22.08.06.png

AWS Lambdaで開発しているので、あとはスケジューリングでもすれば自動起動は簡単ですね。

現状の課題

一応できましたが、いくつかすでにわかっている課題があります。

  1. レコードの一括取得は一度に500件までしかできない。(kintoneの仕様)
  2. レコードの一括登録は一度に100件までしかできない。(kintoneの仕様)
  3. S3へアップロードしたファイルはずっと残ってしまう。(手動で削除しに行かないと後々ゴミになる)
  4. リストアの仕組みがない

この辺を解消するような実装はまた後ほど。

成果物

ここまでの成果物は以下になります。

https://github.com/kojiisd/aws-kintone-backup/tree/v1.0

まとめ

よくあるkintoneアプリケーションのバックアップの一部を自動化することができました。とはいえAPIとしてはプロセス管理やアプリケーションの一般的な設定まで取得するものがあるので、この辺を盛り込むと、どんどんとリッチなバックアップ処理になりそうです。その辺りはまた次にでも。

続きを読む

AWS Lambda から ElastiCache に接続するメモ

やりたいこと

  • AWS Lambda から ElastiCache に接続する
  • boto3 は get/set はできないらしいので他の方法を使う

必要な設定

  • AWS Lambda の実行ロールに ElastiCache へのアクセス権限追加
  • AWS Lambda, ElastiCache を同じVPCにあわせる

redis (クラスターモードが無効) の場合

EC2 にて redis-py をダウンロード

$ pip3 install redis -t ./
  • 生成されるディレクトリを開発環境の index.py と同じディレクトリにコピーする

index.py 作成

index.py
import redis

def handler(event, context):

    r = redis.StrictRedis(host='****.****.0001.apne1.cache.amazonaws.com', port="6379", db=0)

    r.set("key1", "value1")
    print(r.get("key1"))

    return "OK"

以上。

redis (クラスターモードが有効) の場合

EC2 にて redis-py-cluster をダウンロード

$ pip3 install redis-py-cluster -t ./
  • redis-py も依存してるので一緒にダウンロードされる
  • 生成されるディレクトリを開発環境の index.py と同じディレクトリにコピーする

index.py 作成

index.py
from rediscluster import StrictRedisCluster

def handler(event, context):

    startup_nodes = [{"host": "****.****.clustercfg.apne1.cache.amazonaws.com", "port": "6379"}]
    rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True, skip_full_coverage_check=True)

    rc.set("key1", "value")
    print(rc.get("key1"))

    return "OK"

  • クラスターモードが無効の redis には繋がらないのでテスト環境などでは上記実装を切り替える

以上。

続きを読む

boto3を使った一時的なAWS認証情報の取得

概要

IAMロールの切り替えを利用している場合の一時的なAWS認証情報の取得方法について説明します。boto3を使うと、AWS CLIのプロファイル設定をもとに認証情報を簡単に取得することができます。

IAMロールの切り替え

AWS CLIでIAMロールの切り替えを行う場合は、以下のようなプロファイル設定をします。

~/.aws/config
[profile prodaccess]
role_arn = arn:aws:iam::123456789012:role/ProductionAccessRole
source_profile = default

設定内容の詳細については以下のページが参考になります。

IAM ロールの切り替え(AWS Command Line Interface) – AWS Identity and Access Management
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-cli.html

また、IAMロールの切り替え自体については以下のページが参考になります。

チュートリアル: AWS アカウント間の IAM ロールを使用したアクセスの委任 – AWS Identity and Access Management
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html

boto3を使った一時的なAWS認証情報の取得

使用したバージョンは以下の通りです。
* Python 3.6.4
* boto3 1.5.21

boto3は明示的にIAMロールの切り替えを行わなくても、プロファイル設定を見て必要があれば自動的にIAMロールの切り替えを行ってくれます。そのため、以下のような簡単なPythonコードで一時的なAWS認証情報を取得できます。

credentials.py
import boto3

session = boto3.session.Session(profile_name='prodaccess')
credentials = session.get_credentials()

print('export AWS_ACCESS_KEY_ID={}'.format(credentials.access_key))
print('export AWS_SECRET_ACCESS_KEY={}'.format(credentials.secret_key))
print('export AWS_SESSION_TOKEN={}'.format(credentials.token))

このコードを実行すると以下のような出力が得られます。この出力をシェルで評価すると一時的なAWS認証情報として利用することができます。

$ python3 credentials.py
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_SESSION_TOKEN=AQoDYXdzEGcaEXAMPLE2gsYULo+Im5ZEXAMPLEeYjs1M2FUIgIJx9tQqNMBEXAMPLECvSRyh0FW7jEXAMPLEW+vE/7s1HRpXviG7b+qYf4nD00EXAMPLEmj4wxS04L/uZEXAMPLECihzFB5lTYLto9dyBgSDyEXAMPLEKEY9/g7QRUhZp4bqbEXAMPLENwGPyOj59pFA4lNKCIkVgkREXAMPLEjlzxQ7y52gekeVEXAMPLEDiB9ST3UusKdEXAMPLE1TVastU1A0SKFEXAMPLEiywCC/Cs8EXAMPLEpZgOs+6hz4AP4KEXAMPLERbASP+4eZScEXAMPLENhykxiHenDHq6ikBQ==

ただし、この方法では認証情報の有効時間を指定できないため、デフォルトの1時間が適用されます。

認証情報の有効時間(DurationSeconds)については以下に記載があります。

AssumeRole – AWS Security Token Service
https://docs.aws.amazon.com/ja_jp/STS/latest/APIReference/API_AssumeRole.html

続きを読む

AWS S3からpythonで最終更新日時を指定してファイル持ってくる。

AWS S3 からpythonで最終更新日時を指定してファイル持ってくる。

仕事でバージョン指定があったのでPython2.7系で記述。
情報探してたら英語の本家Documentまでたどり着いちゃったので、共有しときます。
ポイントは最終更新日時が.last_modifiedで手に入る点。
日にちだけ所得なら.last_modified.date()

get_file.py

  # -*- coding: utf-8 -*-

  import boto3
  import datetime

  s3 = boto3.resource('s3')

  # s3の対象バケットを選択
  bucket = s3.Bucket('バケット名')

  # 所得したい日付を選択
  target_year = 2018
  target_month = 01
  target_day = 30

  # バケットにあるファイルを全て繰り返し処理
  for obj in bucket.objects.all():

      # 日時条件が一致したら条件突破。今回は指定した日よりも新しければtrue
      if obj.last_modified.date() >= datetime.date(target_year, target_month, target_day):

          # ここでやりたいogj操作。

これで持ってこれた。あとは煮るなり焼くなり。

続きを読む

Boto3 ec2 describe instances tag

6. describe_instances() 2017 Categories AWS, Boto, Python Tags aws, boto3 Search all instances with tag Backup set to handler(event, context): ec2 = boto3 for backup reservations = ec2. import sys import boto3 ec2 = boto3. py”, line 8, in <module> print(instance. describe_instances import boto3 … 続きを読む

AWS Lambda : run EC2 instances in schedule using python

AWS Lambda : run EC2 instances in schedule using python. By: Hit01; 2018年1月25日; カテゴリー: 未分類; Tags: boto3, Java, lamnda, Python, run instances in schedule, プログラミング. 未分類. [商品価格に関しましては、リンクが作成された時点と現時点で情報 · スッキリわかるJava入門第2版 [ 中山清喬 ] 続きを読む

AWS Batchで固定されたEC2インスタンスのGPUを使う

背景

AWS Batchでは、Jobの定義とAMIの指定を行うことで、
1. 自動でEC2インスタンスを立ち上げ
2. Dockerコンテナを起動
3. コンテナ終了時にEC2インスタンスの自動削除
を行ってくれます。

しかし、EC2インスタンスの料金体系では、毎回インスタンスを立ち上げ・削除するのはコスパが悪いため、Jobが実行されるEC2インスタンスを固定して、実行中以外は削除ではなく、「停止」にしたい!

ということで、その手順をまとめました。
AWS Batch、あるいはAmazon ECS(Elastic Container Service)でGPUを使うための設定も含まれていますので、インスタンスの固定はいいや、という方にも参考になれば、と思います。

AWS Batchの設定

Compute environments(=ECS Cluster)

AWS Batch > Compute environments > Create environment から、AWS BatchのJobを実行する計算環境を作成します。この計算環境は、ECSのClusterとして保存されます。(ECS Clusterが自動で生成され、Compute environmentsに紐付けされます。)

  • Managed:
    AMIを指定しておくことで、Jobの実行時にそのAMIで新たにEC2インスタンスを立ち上げてくれます。
  • Unmanaged:
    Compute environmentsに紐付いたECS Clusterを生成します。EC2インスタンスは、自分でそのClusterに登録する必要があります。

作成されたClusterをECSコンソールから確認しましょう。後ほど使います。
<EnvironmentName>_Batch_<ランダムっぽい文字列> のような名前担っているはずです。

Job Queue

Job Queueは、上記で作成したEnvironmentを選択してください。

Job Definition

Volumes

Name Source path
nvidia /var/lib/nvidia-docker/volumes/nvidia_driver/latest

Mount points

Container path Source volume Read only
/usr/local/nvidia nvidia false

ECSで使える(Dockerが動かせる)EC2インスタンスを用意する

ECS用に最適化されたAMIはAWSから提供されていますが、GPUが載ったものでECS用に最適化されたAMIは提供されていません。

用いるAMIを決める

AWS Batchのドキュメント GPU ワークロードの AMI の作成 にある例では Deep Learning AMI with Source Code (CUDA 9, Amazon Linux) を使用していますが、今回は Deep Learning Base AMI (Amazon Linux) を使用することにしました。
(AWS Batchを用いる = Dockerを用いるので、Hostマシンに機械学習ライブラリは不要。 & Cuda, CuDNNのバージョンが複数あるので、動かすDockerImageが変わった時に助かりそう。)
ここは、必要に応じてaws marketplaceでいろいろ探してみるとよいかと思います。

インスタンスを起動する

IAM

インスタンスからECSに向けたIAM Roleが必要です。Roleの設定がない場合は、EC2コンソールから設定をしましょう。

UserData

UserData
#!/bin/bash
echo "ECS_CLUSTER=<ClusterName>" >> /etc/ecs/ecs.config

Dockerが使えるようにする

上記のAMIのままでは、Dockerが使えないので、ドキュメントを参考に、ECSで使える状態のインスタンスを作成していきます。

configure-gpu.sh
#!/bin/bash
# Install ecs-init, start docker, and install nvidia-docker
sudo yum install -y ecs-init
sudo service docker start
wget https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker-1.0.1-1.x86_64.rpm
sudo rpm -ivh --nodeps nvidia-docker-1.0.1-1.x86_64.rpm

# Validate installation
rpm -ql nvidia-docker
rm nvidia-docker-1.0.1-1.x86_64.rpm

# Make sure the NVIDIA kernel modules and driver files are bootstraped
# Otherwise running a GPU job inside a container will fail with "cuda: unknown exception"
echo '#!/bin/bash' | sudo tee /var/lib/cloud/scripts/per-boot/00_nvidia-modprobe > /dev/null
echo 'nvidia-modprobe -u -c=0' | sudo tee --append /var/lib/cloud/scripts/per-boot/00_nvidia-modprobe > /dev/null
sudo chmod +x /var/lib/cloud/scripts/per-boot/00_nvidia-modprobe
sudo /var/lib/cloud/scripts/per-boot/00_nvidia-modprobe

# Start the nvidia-docker-plugin and run a container with 
# nvidia-docker (retry up to 4 times if it fails initially)
sudo -b nohup nvidia-docker-plugin > /tmp/nvidia-docker.log
sudo docker pull nvidia/cuda:9.0-cudnn7-devel
COMMAND="sudo nvidia-docker run nvidia/cuda:9.0-cudnn7-devel nvidia-smi"
for i in {1..5}; do $COMMAND && break || sleep 15; done

# Create symlink to latest nvidia-driver version
nvidia_base=/var/lib/nvidia-docker/volumes/nvidia_driver
sudo ln -s $nvidia_base/$(ls $nvidia_base | sort -n  | tail -1) $nvidia_base/latest

スクリプトの実行(Step 4.)

bash ./configure-gpu.sh

これで環境は完成していますが、念のため動作確認(Step 5. )

sudo docker run –privileged -v /var/lib/nvidia-docker/volumes/nvidia_driver/latest:/usr/local/nvidia nvidia/cuda:9.0-cudnn7-devel nvidia-smi

DockerからGPUが使えることを確認できたら(以下のような $ nvidia-smi の結果が見られれば) OKです。

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-SXM2...  Off  | 00000000:00:17.0 Off |                    0 |
| N/A   43C    P0    42W / 300W |     10MiB / 16152MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

ECS Clusterにインスタンスを登録する

Check 1
インスタンスからECSに向けたIAM Roleが必要です。Roleの設定がない場合は、EC2コンソールから設定をしましょう。

Check 2
Clusterにインスタンスを登録するには、インスタンス側での設定が必要です。インスタンス起動時のユーザデータで下記ファイルがきちんと作られていることを確認。できていなければ作りましょう。

/etc/ecs/ecs.config
ECS_CLUSTER=<ClusterName>

ドキュメント のStep 6., 7.的なことをしてあげると、Clusterにインスタンスが登録されます。(しばらく時間がかかります)

sudo docker rm $(sudo docker ps -aq)

sudo docker rmi $(sudo docker images -q)

sudo restart ecs

Submit Job

お好きなフックでどうぞ

EC2インスタンスを停止する

Managedであれば自動でインスタンスを削除してくれますが、Unmanagedの場合、インスタンスの停止も自分で行わなければなりません。
今回は、Jobの成功、失敗にかかわらず停止したいので、以下のpythonスクリプトを自作し、AWS Lambda x CloudWatchEventsで走らせることにしました。

  • [‘SUBMITTED’, ‘PENDING’, ‘RUNNABLE’, ‘STARTING’, ‘RUNNING’] のいずれかの状態のJobがあればそのまま。
  • いずれの状態のJobもなければ、停止。
import os
import boto3

JOB_NAME = os.environ['JOB_NAME']
JOB_QUEUE = os.environ['JOB_QUEUE']
INSTANCE_ID = os.environ['INSTANCE_ID']


def is_running():
    batch = boto3.client('batch')

    check_status = ['SUBMITTED', 'PENDING', 'RUNNABLE', 'STARTING', 'RUNNING']
    for s in check_status:
        list = batch.list_jobs(
            jobQueue=JOB_QUEUE,
            jobStatus=s
        )['jobSummaryList']
        if list:
            print(f"{[j['jobId'] for j in list]} are still {s}.")
            return True
    else:
        return False


def ec2_stop():
    ec2 = boto3.client('ec2')

    response = ec2.describe_instances(
        InstanceIds=[INSTANCE_ID]
    )
    if response['Reservations'] and response['Reservations'][0]['Instances']:
        state = response['Reservations'][0]['Instances'][0]['State']['Name']

        if state in {'pending', 'running'}:
            print(f"Start to stop the instance: {INSTANCE_ID}")
            response = ec2.stop_instances(
                InstanceIds=[INSTANCE_ID]
            )
        else:
            print(f"The instance {INSTANCE_ID} is not running.")
    else:
        raise ValueError(f'No Such Instances: [{INSTANCE_ID}]')

def lambda_handler(event, context):
    if not is_running():
        ec2_stop()

最後に

こんなことするくらいなら、最初からBatchじゃなくて、ECSにしとけばよかったんちゃうか、と思いながら。
ただ新しいものに触れて、上記以外の学びも得たので、それはまた別の時に。

続きを読む

【完結編】AWS Amazon SageMaker つかってみた4 〜GPU利用でサンプルの実行〜

はじめに

Amazon SageMaker を利用する上で自分が気になったところ、詰まったところを中心にまとめていきます。
この記事では前回の記事で失敗したサンプルの実行について再度まとめます。
前回の記事はこちら
次回の記事は作成中

サンプルの場所

Jupyterの導入については前回の記事を参照

  1. Jupyterのホーム画面を開きます。
  2. sample-notebooks ディレクトリを開きます。
  3. sagemaker-python-sdk ディレクトリを開きます。
  4. mxnet_gluon_cifar10 ディレクトリを開きます。
  5. cifar10.ipynb ファイルを開きます。

サンプルの実行

Jupyter NotebookにはPythonのコードと説明が含まれます。
右に In [ ]: と書かれているのがコードで、それ以外は説明です。

サンプルについて

  • 用いるデータセット: CIFAR-10という6万枚の画像データのデータセット
  • 学習内容: 34層の深層学習により画像を10種類にクラスタリングする。

セットアップ

scikit-image のライブラリが必要なのでcondaのページから入れてください。

  1. conda tab のリンクをクリックするか、Jupyterのホームから Conda のタブをクリックします。
  2. Available packages のところのSearchで scikit-image を検索します。
  3. scikit-image を選択します。
  4. 右矢印を押下してインストールします。

はじめのpythonコードを実行してみましょう。
以下のコードを選択して、画面上部の Cell から Run Cells を選択します。

import os
import boto3
import sagemaker
from sagemaker.mxnet import MXNet
from mxnet import gluon
from sagemaker import get_execution_role

sagemaker_session = sagemaker.Session()

role = get_execution_role()

右の表示が In [*]: に変わります。
* は実行中であることを表します。
実行が終わると In [1]: のような表示になります。
数字は実行された順番を表します。

初めて実行すると以下のWarningが表示されます。
こちらの原因と解消方法を知っている人がいれば教えてください。

/home/ec2-user/anaconda3/envs/mxnet_p27/lib/python2.7/site-packages/urllib3/contrib/pyopenssl.py:46: DeprecationWarning: OpenSSL.rand is deprecated - you should use os.urandom instead
  import OpenSSL.SSL

もう一度実行するとWarningが消えます。

データのダウンロード

CIFAR10をダウンロードします。

from cifar10_utils import download_training_data
download_training_data()

少し時間がかかります。

データのアップロード

Amazon SageMakerで学習させるにはまずS3に学習データをアップロードする必要があります。
S3へデータをアップロードするには sagemaker.Session.upload_data の関数を使います。
戻り値の inputs にはアップロード先のIDが入っており、後ほど学習の際に利用します。

inputs = sagemaker_session.upload_data(path='data', key_prefix='data/gluon-cifar10')
print('input spec (in this case, just an S3 path): {}'.format(inputs))

train 関数の確認

SageMakerで利用可能な学習スクリプトを用意する必要があります。
学習スクリプトは train 関数として実装されている必要があります。
学習環境の情報が train 関数の引数として渡されます。

!cat 'cifar10.py'

学習スクリプトの実行

MXNet クラスを用いると分散環境での学習が可能です。
以下の設定をします。

  • 学習スクリプト
  • IAMロール
  • インスタンス数
  • インスタンスタイプ

デフォルトでは2個の ml.p2.xlarge インスタンスで学習ができるように設定されています。
3行目の train_instance_count の値を 1 に変更して1個のインスタンスで学習するように変更してください。(理由は考察の項目を参照)
※GPUインスタンスはデフォルトで使用制限があります。制限解除方法についてはこちらの記事にまとめています。

m = MXNet("cifar10.py", 
          role=role, 
          train_instance_count=1, 
          train_instance_type="ml.p2.xlarge",
          hyperparameters={'batch_size': 128, 
                           'epochs': 50, 
                           'learning_rate': 0.1, 
                           'momentum': 0.9})

MXNet クラスができましたので、アップロードしたデータを使って学習を開始します。

m.fit(inputs)

学習がスタートしました。
現在実行中の学習はAmazon SageMakerのジョブの画面で確認できます。
学習時間は44分でした。 ml.p2.xlarge が1時間\$1.26ですので学習にかかった費用は\$1ほどです。

予測

学習が終わりましたので学習結果を使ってエンドポイントを作成します。
学習ではGPUインスタンスを利用しましたが、エンドポイントではCPUのインスタンスも利用できます。

deployの戻り値のpredictorオブジェクトでエンドポイントを呼び出してサンプル画像の予測ができます。

predictor = m.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')

CIFAR10のサンプル画像

サンプル画像でCIFAR10のテストをします。

# load the CIFAR10 samples, and convert them into format we can use with the prediction endpoint
from cifar10_utils import read_images

filenames = ['images/airplane1.png',
             'images/automobile1.png',
             'images/bird1.png',
             'images/cat1.png',
             'images/deer1.png',
             'images/dog1.png',
             'images/frog1.png',
             'images/horse1.png',
             'images/ship1.png',
             'images/truck1.png']

image_data = read_images(filenames)

predictorは入力された画像を予測して結果のラベルを返します。
ラベルはfloat値で返ってくるのでintに変換してから表示しています。

for i, img in enumerate(image_data):
    response = predictor.predict(img)
    print('image {}: class: {}'.format(i, int(response)))

リソースの開放

このサンプルの実行が終わったらインスタンスを開放してエンドポイントを削除するのをお忘れなく。

sagemaker.Session().delete_endpoint(predictor.endpoint)

結果

正解率80%

image 0: class: 0
image 1: class: 9
image 2: class: 2
image 3: class: 3
image 4: class: 4
image 5: class: 5
image 6: class: 2
image 7: class: 7
image 8: class: 8
image 9: class: 9

考察

GPUの圧倒的な処理速度

GPUが得意な画像処理ということもあり60倍程度の処理速度が出ていた。

インスタンスタイプ 1秒間に処理できるサンプル数
ml.m4.xlarge 約 22 サンプル/秒
ml.p2.xlarge 約 1400 サンプル/秒

単位時間あたりはCPUよりGPUの方が高いが、処理速度が段違いなので結果的にGPUを使うほうが安上がりになる。

並列処理の同期のオーバヘッドが大きい

MXNetでは1回の学習ごとに各インスタンス間で同期を取っているようです。
単体のインスタンスで学習をさせたところ1秒間に11回の学習ができたのに対し、2並列で学習させた場合は毎秒各インスタンス1回ずつの合計2回しか学習ができませんでした。
分散学習をさせると性能が1/5に低下してしまいます。

インスタンスタイプ 1秒間に学習できる回数
単体(同期なし) 約 11 回/秒
2並列(毎回同期) 約 2 回/秒

今回の学習では1回の学習に要する時間が0.1秒以下の学習であったために毎回同期を取らないといけない並列処理のほうが遅くなる結果となってしまった。

SageMakerのいいと感じたところ

ワンストップで実行可能

ノートブックの作成、リソースの確保、学習、エンドポイントの作成までワンストップで行える。

リソースの最適化

ノートブック、学習、エンドポイントでそれぞれでインスタンスを選択できる。
学習は学習している間だけ料金が発生する。

続きを読む