AlphaZero風オセロにFlaskでWebUIを追加しECS/Fargateで動かしてみた

前々回前回AlphaZero風オセロ強化学習環境を作っていましたが、今回はFlaskでWeb化したゲーム部分を追加してECS/Fargateで動かしてみました。*1

今回のコード

下記、タグv0.0.3になります。

github.com

FlaskによるWebUI

ブラウザでアクセスしてオセロゲームしている様子のイメージは下記です。

f:id:nihma:20190309234827g:plain

実行方法

docker build & runするとFlaskのWebサーバがポート5000で立ち上がります。

README.md

docker build -f Dockerfile_app -t heta-reversi-app:latest .
docker run -d -p 5000:5000 heta-reversi-app

ブラウザでhttp://localhost:5000/にアクセスするとゲームが始まります。オセロ盤の赤丸が石の置ける場所ですのでクリックしてください。

デフォルトの対戦相手のアルゴリズムはランダムです。画面下部のリンクをクリックすると対戦相手のアルゴリズムを変更してゲームを最初から開始できます。*2

f:id:nihma:20190310131219p:plain

実装概要

関連するソース一式は下記です。

.
├── Dockerfile_app
├── Reversi.py
├── app.py
├── data
│   └── model.dat
├── static
│   └── css
│       └── reversi.css
└── templates
    └── index.html

Docker環境はchainer/chainer:v5.1.0-python3 + Flaskの構成です。

Dockerfile_app

FROM chainer/chainer:v5.1.0-python3

COPY . /app
WORKDIR /app

RUN pip3 install --upgrade pip
RUN pip3 install Flask==1.0.2

ENTRYPOINT ["python3"]
CMD ["app.py"]

エントリポイントはapp.pyです。 Base64エンコードしたオセロ盤と次の石の位置を含むjsonをGetパラメータで引き渡す方法にしました。*3

app.py:L17-L19

@app.route('/')
def random():
    return next('/', choice_random, 'random')

app.py:L35-L56

def next(url, choice, algorithm):
    board = Reversi.get_init_board()
    query = request.args.get('query')
    if query:
        params = json.loads(base64.urlsafe_b64decode(query).decode())
        player = Reversi.get_player(np.array(params['board']))
        board = Reversi.put(player, params['num'])
        while True:
            player = Reversi.get_player(board, False)
            if Reversi.is_putable(player):
                choice_data = choice(player)
                board = Reversi.put(player, choice_data['position_num'])
                player = Reversi.get_player(board)
                if Reversi.is_putable(player):
                    break
            else:
                break

    board, is_black, putable_position_nums = Reversi.get_player(board)
    black_num, white_num = Reversi.get_stone_num(board)
    return render_template('index.html',
                           url=url, algorithm=algorithm, is_end_board=Reversi.is_end_board(board), black_num=black_num, white_num=white_num, board=board, is_black=is_black, putable_position_nums=putable_position_nums)

htmlテンプレートはJinja2でtemplates/index.htmlです。

ECS/Fargateで動かす

全体構成は下記です。

f:id:nihma:20190310114836p:plain

ECRにイメージを登録するCodeBuildとイメージが動くECS/Fargateがあります。*4

構築の手順

手順はdocs/setup_app.mdにまとめました。

VPC前々回前回で作成したAWS Batch環境のVPCを使うようにしました。

ECRに登録するイメージを作成する際に含めるAPV-MCTSのモデルファイルはS3にある一番タイムスタンプが最近のファイルにしました。

buildspec_app.yml:L8-13

      - cd data
      - aws s3 sync s3://${MODEL_BUCKET}/data/ ./
      - MODEL_FILE=$(ls -lt model_*.dat | head -n 1 | awk '{print $9}')
      - echo $MODEL_FILE
      - mv $MODEL_FILE model.dat
      - rm model_*.dat

ECS/Fargateのオセロにアクセスする

コンソールの下記からPublic IPが確認できます。

ECS>クラスター>タスク>詳細>Network>Public IP

Public IPがわかればブラウザからhttp://Public IP:5000/でアクセスすることができます。*5

*1:当初はノウハウのあるServerless FrameworkでAPI化してSPAにする構想でしたがChainerなどの外部ライブラリがでかすぎてLambdaの250MB制限を回避できなさそうだったためECSにピボットしました

*2:ランダム以外は重くて実用に耐えられなさそうですが...

*3:手抜きでバリデーションはありませんが

*4:ECS/Fargateへのイメージのデプロイは未対応です

*5:インフラはだいぶ手抜き気味ですmm