Monday, March 6, 2017

AWS 스팟인스턴스에서의 텐서플로 러닝 자동화 (원제: AWS スポットインスタンスでの TensorFlow 学習の自動化)

mizti 님의 AWS Step Functions과 Lambda로 딥러닝 훈련을 전자동화하기 를 수정한 버전입니다.

배경

  • AWS의 고성능 GPU 머신, p2 인스턴스로 딥러닝(텐서플로) 학습을 실행하려 한다.
  • 하지만 p2는 고가!
    • 스팟인스턴스를 사용하면 저렴하다!
      • 하지만 스팟인스턴스는 실행중의 입찰가격에 따라 강제 셧다운된다.
        • 학습중의 데이터를 정기적으로 S3에 업로드하려 한다.
      • 스팟인스턴스를 사용해 학습 시작을 자동화하려 한다.
    • 학습이 끝나면 인스턴스를 셧타운 하려 한다.

사양

  1. 학습시작을 Slack으로 알림
  2. 데이터 저장위치인 S3의 체크
    • bucket은 존재하는가
    • 기존 학습결과를 저장한 디렉터리는 없는가 (덮어쓰지 않도록 체크)
  3. 스팟인스턴스에 대한 입찰 및 결과를 Slack으로 알림
  4. 인스턴스상에서의 데이터다운로드와 학습(혹은 임의 테스크) 실행
  5. 학습 완료 후 Slack으로 알리고 인스턴스 삭제
를 스스로 하도록 합니다.
AWS StepFuncion과 AWS Lambda를 사용합니다.
데이터 취득 방법이나, 학습을 실행하는 커맨드는 Step Function에 인수로써 json을 사용해 전달하므로 딥러닝 학습 외에도 시간이 걸리는 성과물을 파일로 출력하는 작업 등 범용적으로 사용할 수 있습니다.
{
  "exec_name": "test1",                                 // 실행명. 결과를 저장할 디렉터리명
  "bucket_name": "your-output-bucket-name",             // 결과를 저장할 버킷 이름
  "repository_url": "https://github.com/hoge/fuga.git", // 학습용 프로그램이 들어있는 저장소
  "repository_name": "fuga",                            // 저장소 이름
  "data_dir": "fuga/data/",                             // 로컬에서의 학습데이터 저장장소
  "output_dir": "fuga/output",                          // 로컬에서의 학습결과 저장장소
  "data_get_command": "aws s3 cp s3://your-data-bucket-name/hoge/fuga.data fuga/data/",
                                                        // S3등에서의 데이터 취득 커맨드
  "exec_command": "python fuga/train.py",               // 학습실행 커맨드
  "ami_id" : "ami-111111",                              // 재기동할 인스턴스의 AMI ID
  "instance_type" : "p2.xlarge",                        // 재기동할 인스턴스의 타입
  "spot_price" : "7.2"                                  // 스팟 리퀘스트의 최고금액
}

mizti님의 내용과의 차이점

위 내용은 mizti님의 AWS Step Functions과 Lambda로 딥러닝 훈련을 전자동화하기에서 잘 설명되어있으나, 완전히 똑같이 만들어 본 뒤 제 나름대로 다음과 같이 커스터마이징 하였습니다.
  • 알림을 푸쉬가 아니라 Slack으로
    • 전반적인 에러 발생시에도 Slack으로 알림
  • 스팟 인스턴스의 입찰이 완료될 때 까지 반복하여 대기
  • 하나의S3버킷 안에 복수의 학습결과를 차례로 저장
  • 학습완료후 즉시 인스턴스 파기

자동화

전체적인 설명

AWS Lambda를 사용해 S3의 체크, EC2의 스팟리퀘스트 송신~학습 시작, Slack으로의 알림등 개별 작업을 구현합니다.
그것들을 AWS Step Function을 사용해서 조합, 자동화를 실현합니다.

사전 준비

학습용 인스턴스의 AMI를 제작

AWS EC2 - p2 인스턴스에 텐서플로 도입하기 (번역)를 참고해 텐서플로를 실행하기 위한 AMI를 만듭니다.

Lambda용 Role 작성

lambda에서 S3의 상태를 체크하거나 EC2의 스팟플리트리퀘스트를 송신하는 등의 작업에 필요한 Role을 작성합니다.
lambda-training이라는 이름으로 만들겠습니다.
아래와 같이 Policy를 attach 합니다.
여기서 AmazonEC2SpotFleetRole-FleetCreation는 스팟플리트를 작성하기 위한 Policy로 다음과 같이 만들었습니다.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeImages",
                "ec2:DescribeSubnets",
                "ec2:RequestSpotInstances",
                "ec2:RequestSpotFleet",
                "ec2:TerminateInstances",
                "ec2:DescribeInstanceStatus",
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
각 서비스 별 보안 정책에 맞게 권한을 적절하게 조절하는 것이 좋습니다.
신뢰관계로는 lambda를 지정해둡니다.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

작업실행을 위한 EC2 Role 작성

학습을 실행할 EC2는 데이터를 S3 등의 저장소로부터 다운로드/업로드 할 필요가 있기에 적절한 권한을 지정한 Role을 작성해야합니다.
ec2-deep-learning으로 이름붙이겠습니다.
관리 Policy에 AmazonS3FullAccess등을 추가해둡니다.
신뢰관계는 EC2를 설정합니다.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

스팟플리트Role 작성

이것은 한번이라도 AWS Console에서 스팟리퀘스트를 송신한 적이 있다면 그 때에 작성되는 것 같습니다.
AmazonEC2SpotFleetRole Policy를 attach해, spotfleet.amazonaws.com에 신뢰관계를 갖고 있는 Role입니다.
aws-ec2-spot-fleet-role이라고 이름붙이겠습니다.

암호화 키 작성

Slack의 WebHook URL등의 정보를 암호화하기 위해 AWS의 암호화키를 만듭니다.
  1. IAM 좌측 메뉴의 '암호화키' 선택
  2. 리전을 EC2와 같은 것으로 선택 (중요)
  3. '키 작성' 클릭

이미지는 만들어진 후의 모습을 보여줍니다..
다음과 같이 작성합니다.
  • alias: lambda-training 등
  • 키 관리자: 관리권한을 갖게 하려는 사용자를 적당히 (이 작업을 진행중인 자신 등)
  • 키 유저: 방금 전 만든 lambda-training Role을 체크
이를 통해 lambda-training Role에서 실행되는 AWS Lambda가 암호화 · 복호화를 할 수 있습니다.

lambda-training Role이 lambda-training 키를 사용할 수 있도록 설정

이름이 같아 까다롭지만, Role에도 설정을 추가하여 키를 사용할 수 있도록 합니다.Role의 lambda-training 인라인 Policy에 다음을 추가하십시오.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "lambdakms",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:us-west-2:111111111:key/1111-1111-1111"
            ]
        }
    ]
}
Resource부분은 lambda-training 키의 arn을 지정합니다.

AWS Lambda의 구현

AWS Console 상에서 Lambda function을 구현합니다.
halhorn가 사용하고 있는 lambda나 step function은 https://github.com/halhorn/AWSAutoLearning에 올려두었으므로 그쪽을 참조 부탁드립니다. 마지막의 .py는 필요하지 않습니다.
(대부분의 코드는 원래의 mizti님 것을 사용해 주세요.
AWS Lambda 내에서의 AWS 조작에는 boto3를 사용합니다.
사용상의 레퍼런스가 필요하시면 Boto3 - EC2나 Boto3 - S3을 참조하시면 좋습니다.
Lambda는 lambda_handler을 엔트리 포인트로 실행되어 json으로 넘겨진 인수가 이벤트 변수에 들어간 상태로 실행됩니다.
또한, event를 마지막으로 return 함으로써, 그 내용을 step function에서 정의 된 다음 lambda에 전달하는 것이 가능합니다.
작성한 lambda는 적당한 json을 인수로 넣어 테스트함으로써 확인을 해 보는 것이 중요합니다.
여기에서는 특히 주의가 필요한 것들에 대해서만 설명합니다

request_spot_fleet

플릿 리퀘스트를 하고, 인스턴스가 실행된 후 학습 코드를 실행하기 위한 Lambda 입니다.
FLEET_ROLE이나 EC2_ROLE 등 정수부분을 적절하게 설정해 주세요.
KEY_NAME은 인스턴스 작성시에 사용하고 있는 키 페어 명을 지정해 주세요.

EC2 재기동시의 처리

create_user_data 안을 편집하는 것으로 EC2 실행 후의 학습실행 커맨드를 바꿀 수 있습니다.
이 커맨드는 root 권한으로 실행됩니다. 그 때문에 일일이 sudo -u ubuntu ** 을 사용합니다.
또한 sudo -u ubuntu -i COMMAND 를 이용해 .zshenv 등도 로드된 상태에서 커맨드 실행이 가능합니다. 하지만, 이것은 현재 디렉터리가 ubuntu의 home인 상황에서 실행되므로 주의하세요. 또한, 제 환경에서는 .zshrc를 로드할 수 없었습니다.
실행되는 것은 다음과 같습니다.
  • 정기적으로 학습 결과 등을 S3에 업로드 하는 cron 작성
  • 데이터 취득
  • 학습 실행
  • complete.log 라고 하는 학습완료 플래그가 되는 파일 생성
    • 이것이 S3에 업로드 됨으로써 Lambda가 학습을 완료했다는것을 알게 되는 구조입니다.

테스트

다음과 같은 인수로 테스트 할 수 있습니다.
{
  "exec_name": "test1",
  "bucket_name": "your-output-bucket-name",
  "repository_url": "https://github.com/hoge/fuga.git",
  "repository_name": "fuga",
  "data_dir": "fuga/data/",
  "output_dir": "fuga/output",
  "data_get_command": "aws s3 cp s3://your-data-bucket-name/hoge/fuga.data fuga/data/",
  "exec_command": "python fuga/train.py",
  "ami_id" : "ami-111111",
  "instance_type" : "p2.xlarge",
  "spot_price" : "7.2"
}
디렉터리는 홈 디렉터리에서 git clone 한 것으로 전제합니다.
절대경로로 쓰면 /home/ubuntu/fuga/... 와 같은 모습입니다.

send_notification / send_error_notification

작업의 진행 등을 Slack 알림을 사용합니다.
우선, 사전에 InCommingWebHook를 Slack으로부터 취득해 두세요.이런 모습입니다.
다음으로, Webhook을 평문 그대로 사용하는 것은 보안상 좋지 않기에 암호화 한 뒤 환경변수에 포함합니다.
코드 하단에 환경변수를 포함할 장소가 있기때문에 여기에서 slackChannel과 kmsEncryptedHookUrl을 설정해 주세요. 단, url은 https://을 제외해 주세요. 또한 이 경우 '암호화 Helper를 활성화'에 체크해야 합니다.

암호화 키는 이전에 만들어둔 「lambda-training」을 지정합니다. 만약 여기서 lambda-training가 보이지 않는다면 키를 만든 리전이 다르거나 Role 설정이 잘못되었을 가능성이 있습니다.
다음은 '암호화'를 클릭해 kmsEncryptedHookUrl만 암호화 합니다.

AWS Step Function의 구현

각각의 Lambda가 완성되면, 그것들을 합쳐 Step Function을 구현합니다.
이것은 json으로 기록되며 작업의 종류, 실행내용, 다음 무엇을 할 것인가 등을 지정합니다.
Create a State Machine을 이용해 작성합니다.
한번 만들면 편집할 수 없는 것 같습니다.

기동

개별 Lambda를 테스트로 확인했다면 StepFunction은 잘 움직여 줄 것입니다.
다음과 같은 분위기의 데이터로 실행해봅시다.
{
  "exec_name": "test1",
  "bucket_name": "your-output-bucket-name",
  "repository_url": "https://github.com/hoge/fuga.git",
  "repository_name": "fuga",
  "data_dir": "fuga/data/",
  "output_dir": "fuga/output",
  "data_get_command": "aws s3 cp s3://your-data-bucket-name/hoge/fuga.data fuga/data/",
  "exec_command": "python fuga/train.py",
  "ami_id" : "ami-111111",
  "instance_type" : "p2.xlarge",
  "spot_price" : "7.2"
}
또한 개인 Github 저장소를 사용하는 경우에는 키 정보를 암호화 키를 사용해 보안을 확보하거나, 액세스키를 이용해 repository_url에 연결하는 등의 방법을 사용하면 됩니다.

순서대로 잘 진행되네요! 
고생하셨습니다. 





번역된 컨텐츠입니다.
Qiita. by halhorn. http://qiita.com/halhorn/items/ae402e8c22bc1083ff23

Translated article.
Qiita. by halhorn. http://qiita.com/halhorn/items/ae402e8c22bc1083ff23

No comments:

Post a Comment