Splatoon2のウデマエがやっとXに到達した

一番好きなルールのガチエリアでウデマエXに到達したので、結構うれしい。

記録ではSplatoon2でS+に到達したのは約一年半前ということなので、ウデマエXに上がるのに大分苦戦したことが分かる。なお、ガチヤグラとガチホコのウデマエは当時と一緒というのが笑うポイントです。

デュアルスイーパーカスタムとか他のブキもあれこれ使ったけど、最終的にSplatoon 1の頃から愛用していたわかばシューターが一番成績がよいというのも面白い。打ち合いは苦手な一方、デスせず塗ることやボムでのキルは得意という自分の性質を生かそうとすると、わかばシューターを選ぶのは納得感はある。

ウデマエXに上がるまでにやったこと

当たり前のことを当たり前にやると、少なくともガチエリアに関してはウデマエXには行けた。

  • うまい人の動画を見る
    • 例: https://www.youtube.com/watch?v=NnL4qaijaQ0
      • この方の解説動画は多分全部かつ何回も見て参考にさせてもらいました
    • エイムがはちゃめちゃうまい人の動画は真似できないので、立ち回りがうまい人の動画を見つけて参考にするのがよかった
  • 味方と相手の編成をしっかり確認する
    • 味方に塗り要因が全然いないからキルより塗りを優先しようとか、編成事故で相手にしか長射程がいないのに何も考えないと一方的にキルされるので、ボムの牽制を意識的に多めにしようといったことを意識する
    • 余裕があれば敵のギアも見ておく。イカニンジャがいるとかステジャンがいると頭に入れておくと、多少は警戒できる
  • スペシャルはとにかく味方と合わせる
    • Splatoon 2では1の頃と比べるとスペシャルが弱くなっているので、一人で打開しに行くのは無謀
    • カウント取り替えすまで多少時間がかかっても、味方の復帰を待ってからスペシャルを吐くようにしよう。打開の成功の確率がぐっと上がる
    • わかばのスペシャルはアーマーなので、自分がリードしているときは無闇にスペシャルを使わず、敵のマルチミサイルやハイパープレッサーに対してカウンター気味に発動すると味方の生存率が高まって敵の打開を阻止しやすい
  • 立ち回りでカバーできないところはギアでカバー
    • 自分の場合、どうしても長射程に対してメインを使って距離を詰めるというのが下手でカバーしきれなかった
    • サブ性能アップを積んでボムの飛距離を伸ばし、長射程がいても対抗できるようになり、目に見えて成績が上がった
  • 常に味方と敵の生存人数を確認する
    • 勝ってても味方が2人落ちたから少し前線を下げようとか、敵が3落ちしたから一気に前線を上げようといったことを考える
  • 暇があったらとにかくマップを見る
    • マップのインクの動きで裏取りにきてるなとか、味方はこっちから打開しようとしているから合わせようとか相手の姿を目視する前にできることはたくさんある
    • サウンドプレイとかは自分はうまくできなかった
  • 味方のカバーをなるべく早くする
    • S+の上位になってくると、とにかくカバーが早い。味方が仕留め損なった敵をボムで追撃するとかそういったことをやって、局所的に数的優位な状況を作り出す
    • 一人で無双はできるものじゃないので、ツーマンセルを組んで動くようなイメージ
  • 壁は左に置く
    • イカちゃんは右利き。相手と対面する場合は壁を左に置いておくと一方的に有利に射撃することができる

AWS Fargate上でMackerelのプラグインを実行 & 監視する

特定のホストの紐付かないメトリックをMackerelのプラグイン経由で監視する場合、AWSであればEC2上に監視専用のホスト(いわゆるmonitoringホスト)を立てることが多いです。これは便利な一方で、以下のような問題点があります。

  • EC2ホストの面倒を見ないといけない
  • 趣味で使う用途だとEC2のホスト台を毎月あまり払わずに済むとうれしい

特に一番最初の面倒を見るコストが一番大きいわけですが、サーバーレス/コンテナ時代なので、いいソリューションがあればそれに乗りたいわけです。先行研究がすでに存在していて、AWS Lambdaで試されている方がおられます。

自分でも似たようなことをAWS Fargate上でやっているのですが、せっかくなのでどんな感じでやっているか簡単に書いておきます。

mkrおよびプラグインが入ったdockerイメージを作る

mkrやMackerelプラグインを入れるDockerfileを書きます。

FROM centos:centos7

RUN yum install -y curl sudo
RUN curl -fsSL https://mackerel.io/file/script/setup-yum-v2.sh | sh
RUN yum install -y mackerel-agent mackerel-agent-plugins mackerel-check-plugins mkr
RUN mackerel-agent init -apikey='XXXXXXXXXXXXXXXX'

# install plugin
RUN mkr plugin install tjinjin/mackerel-plugin-aws-billing
RUN mkr plugin install mackerelio/mackerel-plugin-aws-batch
RUN mkr plugin install mackerelio/mackerel-plugin-aws-step-functions

WORKDIR /app
ADD monitor /app/monitor
CMD /app/monitor

Fargate上で実行するshellスクリプトを用意

プラグインを実行して、その出力をmkrを使ってMackerelにメトリックとして投稿します。サービスメトリックとして投稿しても、ホストメトリックとして投稿してもよいと思います。

#!/bin/bash
ACCESS_KEY_ID="XXXXXXXX"
SECRET_ACCESS_KEY="YYYYYYYYYY"
MACKEREL_HOST_ID="ZZZZZZZZZ"

/opt/mackerel-agent/plugins/bin/mackerel-plugin-aws-billing -access-key-id=${ACCESS_KEY_ID} -secret-access-key=${SECRET_ACCESS_KEY} | mkr throw --host ${MACKEREL_HOST_ID}
/opt/mackerel-agent/plugins/bin/mackerel-plugin-aws-batch -access-key-id=${ACCESS_KEY_ID} -secret-access-key=${SECRET_ACCESS_KEY} -job-queue=go-active-learning -region=us-east-1 | mkr throw --host ${MACKEREL_HOST_ID}

まだ存在しないmonitoringホストを指定したい場合、API経由で新規ホストとして登録するとよいでしょう。

以上をbuildして、ECRにイメージを登録します。

タスクを定期的に実行する

先ほど書いたshellスクリプトを対象にしたタスクを定期的に走らせます。私の場合はCloudwatchイベントで定期的に走らせています(趣味のものなので、例えば1時間に1回とかの頻度で特に困らない。どうせすぐには対応できないことも多いので...)。

そもそも論として

Mackerel公式のAWS/Azureインテグレーションが充実すれば、ユーザーがこういったmonitoringホストを立てる工夫をする必要がなくなるという話があって、それはその通りだと思います(著者はMackerelの開発者の一人です)。インテグレーションも最近どんどん増やしていっていますが、インテグレーションで対応しきれないすごくニッチなメトリックを取りたいというケースは今後もmonitoringホスト相当のものが必要なケースはあると思います。そういったときはAWS Lambdaや今回紹介したようなFargateを使った方法を試してもらえるといいかもしれません。

2018年の砂場活動振り返り

インフラやミドルウェアにとにかく苦手意識があるが、仕事的にいつまでもそう言ってられない。そこで、最悪全部ぶっ壊れても大丈夫な砂場を作り、そこを土台に活動をするというのを2018年の目標に設定していた。

結構な時間をかけたこともあり、それなりの砂場と活動ができて、自分としても勉強になってよかった点が多かったので振り返りを書きます。一個一個ちゃんとエントリ書いていたので、振り返りが楽で助かった。

完成系はML Newsだけど、2018年1月時点では

  • そもそもWebアプリですらなくCLIアプリだった
  • データの管理もデータベースではなくテキストファイル

という素朴な作りだった。

インフラ編

最初はCLIアプリをWebアプリにする活動をやったが、その後はAWS上にインフラ部分の構築を進めた。

次に一台のEC2をAWSコンソールから立てて、sshでログインしてyumコマンドを打って...という10年前くらいのサーバー構築をやった。しかし、EC2のスペックを変えて作り直すのはあまりにもダルかったので、ansibleでミドルウェアのインストールや必要な設定をやらせるように。

一台のEC2にWebアプリもredisもpostgresも同居してという状況だと辛い状況(公開する関係でWebアプリはpublicサブネットに置きたいけど、DBもpublicサブネットに置いてる...)になるのは目に見えていたので、managedサービスが存在するものはそれを使うようにした。AWS上でのリソース構築は会社でCloudFormationを使うことが多かったので、自分も使うことにした。

CloudFormationはまあ書くだけという感じだけど、VPCやサブネット、セキュリティグループをどう設定するとよいかをイチから自分で考えるのは普通に勉強になったので、やってよかったですね。CloudFormationのテンプレートはどういう粒度で分割すればいいのかは未だに分からない。CloudFormationのyamlを編集するのも辛くなってきたのでaws-cdkも試しましたが、こちらはまだまだ開発中という感じだったのでしばらく放置しています。

WebアプリをEC2上に立てていたが、世の中コンテナ化が進んできていてどういうよいところ悪いところがあるのか知りたい自分も知りたい、仕事的にも知っておく必要があるということでコンテナ化を進めた。

最初は自分でEC2を立ててECSのクラスタにjoinさせる、という形を取っていたが「いい加減EC2の管理をしたくないなぁ...」と思ってきたため、途中からFargateを使うように。FargateだとEC2を立てておかなくてもdesired-countを変えればアプリケーションサーバーの個数も変えられるので楽になった一方、EC2より若干お高いとかログ周りが微妙(fluentdとの連携とか)、Fargateの起動は若干もたつくなどが試して分かってきました。この辺から必要なリソースをある程度自由に変更できるようになってきたため、砂場感が出てきました。

コンテナ化したご利益でAWS Batchも使えるようになりました。普段のWebアプリのワークロードと異なる機械学習のモデルの学習などはAWS Batchに投げ込むことができるようになったため、バッチが走っている間レイテンシーが落ちる...といったことを心配しなくてよくなりました。AWS Batchは仕事でも使っていますが、便利ですね。

AWS Batchで複数のジョブを管理していると、そのバッチの順序依存関係をcron職人芸ではなくもっと賢い方法で管理したくなりました。同僚が使っていたStep Functionsが便利そうだったので導入したのもこの頃でした。試した内容はLTで発表も行ないました。

機械学習をやるためにデータ基盤周りのことを考えることが増えてきましたが、その辺も試してみようということでCloudWatch LogsからKinesisを通してS3にログを転送して、Athenaで分析するといったこともやりました。やってみたのはよかったですが、ログの流量がまだまだ少ないのでどういうところで困るかはまだまだ見えてこないという状況ではありました...。

ミドルウェア編

インフラ編である程度下周りは整備したので、パフォーマンス改善の活動を通してミドルウェアのことを知る活動も行ないました。ISUCON勢には当たり前すぎるところから、という感じではあります。当たり前のことを当たり前にできるようになっていきたい。

チューニングをした結果、改善したのかそうでないのかを判断するためのモニタリングをやれるようにしたり、DBで必要なインデックスを張るとか、コネクションプールを適切に設定するとか、そもそもボトルネックきちんと把握できてる??といったことを少しずつやっていきました。AWSの課金額が順調に増えてきてしまったのもこの辺だ😇

チューニングではないけれど、Nginxの設定をあれこれいじったりということもやっていました。

フロントエンド編

砂場活動のメインの目的はインフラ方面の知識および経験の強化ですが、Webアプリを作る過程でフロントエンドでも勉強になりました。Vue.jsを導入してSPAにしたり、Cognitoを使って認証させたりといったことをやっていました。以前は趣味でjavascriptを書く機会が皆無だったので、趣味でもjavascriptを書く機会ができたのはよかったなと思います。当初は特に予定がなかったサーバーサイドレンダリングも導入成功していた。

まとめ

2018年の砂場活動を振り返りました。正直、よい設計に落し込むまでめちゃくちゃ遠周りしているし、AWSへの月々の課金も無視はできない金額になっています(現実的なところに落と込んだら課金も減りつつある)。しかしながら、以下のような大きなメリットがありました。

  • 遠回りするとどのアプローチがどうダメだったかが身を持って分かる
    • 多少痛い目に合わないとどれくらい困るかがイメージが湧かない性格...
  • 多少データがぶっ飛ぼうが困るのは自分だけなので、思い切ってあれこれ試せる
    • 精神的な障壁を低くするという砂場活動の目的を十分果たせていたと思います

インフラ/ミドルウェアの苦手意識はまだまだ払拭できてはいませんが、2018年に作った砂場を足場に2019年には多少の武器にしていけるようにしていきたいなと思います。

飽きっぽい自分としては、年始に立てた目標を年末までちゃんと継続的に取り組めていたことに驚いていますが、エンジニア1on1で適切にフィードバックをくれたid:shiba_yu36さんの力が大きかったなぁと思います。ありがとうございます!

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

最近の砂場活動その11: Nuxt.jsでサーバーサードレンダリング

タイトルの通りやりましたというお話です。以前は$http_user_agentを見て、googlebotだったらrendertronを経由させてレンダリングした結果を見せることでお茶を濁して凌いでいた。

この方法、SPA自体は全く変更する必要なくbotにレンダリングされた結果を見せることができるというメリットがある一方で、以下のようなデメリットもあること分かってきた(分かってはいたものもある)。

  • レイテンシーが大きい: rendertronが結果を返すまでに3-4秒程度かかる
    • rendertron自体に手は入れたくないため、お手軽な改善方法がない
    • botにならある程度レイテンシーかかっても問題ないという話はありそう
  • rendertronがそこそこメモリを食う
    • めちゃくちゃ食うというわけでもないが、メインのAPIサーバーよりメモリを食っていて何だかな感がある
  • 人間とbotにhtmlを出し分けている
    • 描画されるものが決定的に異なるわけではないが、若干気持ち悪い
    • 両方のテストが必要になる

rendertronで対応するのはそもそも邪道だなとは思っていたので、Nuxt.jsで真面目にサーバーサイドレンダリングを行なうようにした。変更に対応するPull Requestは以下。

package-lock.jsonを入れていなかったのでdiffがやたら大きいですが、本質的にはhtmlテンプレートファイルの置き場所が変わったくらいです。

対応方法

元々はVue.jsを使ったSPAだったので、そこまで大きな変更点はないです。覚えている範囲でNuxt.js用に変えた点としては以下の通り。

  • テンプレートファイルの置き場所変更
    • Nuxt.jsはlayoutpagescomponentsなどある程度ディレクトリに関して規約があるため、それに従うようにします
    • 特に整理できていなかったので見通しはよくなった
    • routingも基本的にpagesのディレクトリに従う形
  • クライアントサイドでデータ取得している箇所をサーバーサイドで取得するように
    • メインの変更点。asyncDataを使うようにする
    • Vue.jsのときはrouterに必要な情報が入っていたが、Nuxt.jsではcontextに必要な情報が入っている。context.route.params.MyParamという感じ。あまりドキュメントに書かれていないので、console.logで出力しながら必要なものを探した
  • htmlのmeta情報の置き換え
    • vue-headfulを使っていたが、Nuxtが面倒を見てくれるようになったのでそちらを使う
  • 設定をnuxt.config.jsに書く
    • webpackであれこれ書いていたこと(minifyとか)をnuxtがある程度面倒を見てくれるようだった
    • そもそもwebpackも雰囲気でしか書けていなかった...
  • Node.jsで動かない記法やライブラリの置き換え
    • developer toolに出力させているつもりが、terminalに出ていたというのがちょくちょくあったので、クライアントサイドで動くものかサーバーサイドで動くものかはきっちり意識しながら書く必要がある

これで大体動いた、Fetch as Googleで確認。慣れは必要だけど、めっちゃ大変という感じではない。Vue.jsをある程度触ったことがあれば大丈夫。初回のhtmlはサーバーサイドで生成して、それ以降の画面遷移はSPAのときと同じような感じで動くので、SPAで書いていたものもほとんど無駄にはならなかった。

rendertronはなくせたものの、サーバーサイドでレンダリングするNode.jsは相変わらず必要。AWS ECSで動かしているので、専用のDockerfileを用意する。といっても必要なファイルをコピーする、くらいしかやっていない。ECSのタスク定義にもよしなに修正を入れる。

FROM node:11.6.0-slim
ENV NODE_ENV production
WORKDIR /app
COPY .nuxt /app/.nuxt
COPY ./nuxt.config.js /app/nuxt.config.js
COPY ./package.json /app/package.json
COPY ./package-lock.json /app/package-lock.json
RUN npm install 

ENV HOST 0.0.0.0
EXPOSE 3000

ENTRYPOINT ["npm","run-script","start"]

静的ファイル(今回の場合はjsしかないけど)はnginxで配信させるようにしたかったので、nginxも適当に書き換える。

upstream local-nuxt {
    server localhost:3000;
}

server {
    location / {
        proxy_pass http://local-nuxt;
    }
    location /_nuxt/ {
        root /www/app;
        proxy_set_header Host $http_host;
        expires 1d;
        gzip on;
        gzip_types text/css application/javascript;
    }
}

docker-componentsを頑張って書いて手元でNode.js/APIサーバー/Nginxがそれぞれ連携して動いているかを確かめられるようにすべきと思いつつ、面倒くさくなって本番に導入しながら確認した。

導入後の所感

  • Nuxt.jsなかなかよい、規約などに従ったほうがカオスにならないのでサーバーサードレンダリングせずにSPAするだけでも導入して損はあまりなさそう
  • 人間とbotに出し分けしなくてよくなったので、見ないといけないポイントが減った
  • rendertronと比較してレイテンシーは改善された(そりゃそうだ)
  • とはいえ、短時間にアクセスが集中すると200を返せなくなるときが増える

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで

  • 作者: 川口和也,喜多啓介,野田陽平,手島拓也,片山真也
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/09/22
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

最近の砂場活動その10: songmu/horensoを使ってバッチの処理時間をMackerelのサービスメトリックに記録する

機械学習の前処理をやるバッチの処理時間が以前は20分くらいだったのにいつの間にか2時間かかっていた...ということがありました。処理時間を短かくする修正は入れたものの、処理時間がどれくらいかかっているかを継続的に監視する仕組みがそもそもないのが厳しいなと思ったのでそれについて書きます。完全にN番煎じなので、特に新規性はないです。

songmu/horensoとは

バッチの処理時間を記録したいけど、大掛りな仕組みは導入したくない。horensoでは実行したいバッチをwrapするような形で簡単に導入できる。

実行したい元々の処理が以下だとすると

/app/go-active-learning.bin add --input-filename input.txt

こんな感じでラップすればよい。

horenso -t add_recent_urls -T --reporter "/app/mackerel_service_metric_reporter go-active-learning-staging" -- /app/go-active-learning.bin add --input-filename input.txt

reporterは終了時に実行されるコマンド。自分の好きな言語で書ける。開始時刻と終了時刻から実行時間を計算して、Mackerelのサービスメトリックに投稿するreporterをGolangで書いた。horenso自体もGolangで書かれているし、MackerelのAPIをGolangから叩くmackerel-client-goもあるので、Golangだとしゅっと書ける。

失敗したときにSlackに送るreporterとかも追加で指定することができる。元々のコマンド自体は何も変える必要がないのでgood。

Mackerelで監視する

実行時間がX分以上だったらSlackに通知する、といったreporterを直接書いてもよい。しかし、Mackerelに実行時間を投稿しておくと過去の傾向が分かるし、X分以上だったらSlackに通知するといった処理をMackerelに任せることができるので気楽。

最近の砂場活動その9: Cognitoで認証済みのユーザーでAPI Gatewayを呼び出す

ML NewsのWeb UIにアノテーションボタンを付けたい、でもユーザー管理機能は作りたくない(アノテーションするのは自分だけなので)。AWSのCognitoとAPI Gatewayを組み合せると簡単にできると聞いたので、やってみました。色々てこずったので正直あまり簡単ではなかったけど、やりたかったことはできたのでメモとして書き残しておきます。

やりたいことは以下。

  • AWSの仕組みでいい感じにユーザー認証されたい
    • 認証の仕組みを自前でやりたくない
  • 認証されたユーザーのみが叩けるエンドポイントを工数少なく実現したい

API Gatewayで所望のエンドポイントを生やす

認証をどうやるかは後回しにして、ひとまずエンドポイントをAPI Gatewayで生やしていきます。API Gatewayに生やしておくと後々の認証(Cognito)との接続がよいので、これを使います。

API Gatewayが何かを一言で説明するのは難しいけど、今回の用途に限れば裏側のロジックをLambdaで書きつつ、それをhttpで叩けるエンドポイントになってくれる君と思っておけばよい。別にlambdaじゃなくてもよいけど、今回はlambda。その他、リクエストをキャッシュさせたりといったこともできるようだが、今回の用途にはあまり関係ない。

ロジックをLambdaに書く

処理の本体はLambdaに書きます。Lambdaは色んな言語で書けるけど、手慣れているGolangで書きます。

Lambdaに書くロジック

package main

import (
    "net/http"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-lambda-go/events"
    "os"
    "log"
    "fmt"
    "encoding/json"
)

type MyRequest struct {
    Url string `json:"url"`
    Label model.LabelType `json:"label"`
}

var errorLogger = log.New(os.Stderr, "ERROR ", log.Llongfile)

func UpdateExampleLabel(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    myReq := new(MyRequest)
    if err := json.Unmarshal([]byte(req.Body), myReq); err != nil {
        return clientError(http.StatusUnprocessableEntity)
    }
    // ここにロジックを書いていく

    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusOK,
        Body:       `{"hoge": 1}`,
        Headers: map[string]string{
            "Access-Control-Allow-Origin" : "*", // CORS対策
        },
    }, nil
}

func serverError(err error) (events.APIGatewayProxyResponse, error) {
    errorLogger.Println(err.Error())

    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusInternalServerError,
        Body:       http.StatusText(http.StatusInternalServerError),
    }, nil
}

func clientError(status int) (events.APIGatewayProxyResponse, error) {
    return events.APIGatewayProxyResponse{
        StatusCode: status,
        Body:       http.StatusText(status),
    }, nil
}

func main() {
    lambda.Start(UpdateExampleLabel)
}

API Gatewayの設定

ロジックが書けたらaws-sam-cliを使って手元でテストしたりするとよい。sam local start-apiとかやると手元でサーバーが走ります。

動作確認ができたらsamを使ってLambdaにデプロイしていく。今回の用途ではprivate subnet内のRDSに接続する必要があったため、VPC内にLambdaを作った。samのテンプレートはこんな感じ。

samのテンプレート

  UpdateExampleLabel:
    Type: AWS::Serverless::Function
    Properties:
      Description: "事例のラベルを更新する"
      Runtime: go1.x
      Handler: update_example_label
      CodeUri: update_example_label/build # ビルドファイル設置ディレクトリ
      Timeout: 10
      Policies:
        - VPCAccessPolicy: {}
      VpcConfig:
        SecurityGroupIds:
          - Fn::ImportValue:
              !Sub "${VPCStackName}:BatchSecurityGroup"
        SubnetIds:
          - subnet-xxx
          - subnet-yyy
      Environment:
        Variables:
          POSTGRES_HOST: XXXXX.YYYYY.us-east-1.rds.amazonaws.com
      Events:
        GetEvent: # 任意のイベント名
          Type: Api
          Properties:
            Path: /update_example_label
            Method: post
  LambdaPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      FunctionName: !Ref UpdateExampleLabel
      Action: "lambda:InvokeFunction"
      Principal: "apigateway.amazonaws.com"

samをデプロイすると、API Gatewayのリソースも作られていると思う。

API Gatewayで作ったエンドポイントと元々のドメイン(www.machine-learning.news)は異なるため、CORS(Cross-Origin Resource Sharing)を有効にする必要がある。API Gatewayの画面から「CORSの有効化」を選択、200のレスポンスヘッダーに以下のものを追加しておく。

Cognitoの設定

ここここを参考にしながらAWSコンソールでボタンをポチポチ押していく。ユーザープールとIDプールを作成していく。注意するところとしてこんな感じかな。

  • 認証プロバイダはCognitoだけで特に問題ない
  • ユーザーの登録はcliでやっておく。そうしないとパスワード変更画面を作らないといけなくなるので、一手間増えてしまう
    • 新規ユーザーを受け付ける場合はちゃんと画面を作りましょう...
    • アカウントのステータスがCONFIRMEDになっていればok
  • 全般設定 => アプリクライアントからアプリクライアントを作っておく
    • アプリの統合 => アプリクライアントの設定から「有効なIDプロバイダ」でcognitoを選択しておく

Amplifyで認証されたユーザーのみにアノテーションボタンを見せる

作成したユーザーがサインイン/アウトする画面はぐぐると色々出てくると思うので、それを参考に作る。今回は新しくユーザーがサインアップできる必要はなく、自分用のユーザーはcliで作成しているのでサインアップ画面は特に作らない。currentSessionかどうかを見てisAdminをtrue or falseにして、それによって出し分けする。

ボタンの出し分けに必要なJSの断片

import Amplify, {
  Auth,
  API,
} from 'aws-amplify';

Amplify.Logger.LOG_LEVEL = 'DEBUG';
Amplify.configure({
  Auth: {
    region: 'us-east-XXX',
    identityPoolId: 'us-east-XXX:YYY',
    userPoolId: 'ZZZ',
    userPoolWebClientId: 'AAAAA',
  },
});

export function IsAdmin() {
  return Auth.currentSession().then(_ => true).catch(_ => false);
}

export default {
  data () {
    return {
      ...,
      isAdmin: false,
    }
  },
  mounted() {
    IsAdmin().then(isAdmin => this.isAdmin = isAdmin);
  },
}

Cognitoで認証されたユーザーのみAPI Gatewayを呼び出せるようにする

画面の上では認証されたユーザーのみにアノテーションボタンを見せられるようになったが、API Gateway自体はインターネットで公開されているエンドポイントになっているため、誰でもエンドポイントを叩けばアノテーションをいじれてしまう。それでは困るので、API自体もCognitoで認証済みのユーザーのみ叩けるようにする。

API Gatewayの設定

Cognitoを設定したときに自動的に作られてるIAM Role(arn:aws:iam::XXXXX:role/Cognito_YYYYYAuth_Roleのようなやつ)に追加で権限を付与する。invokeの権限を今回許可したいエンドポイントのみに与える。

{
    "Sid": "VisualEditor1",
    "Effect": "Allow",
    "Action": "execute-api:Invoke",
    "Resource": "arn:aws:execute-api:us-east-1:XXXXX:YYYYY/*/POST/update_example_label"
}

次にAPI Gatewayの画面のオーソライザーから「新しいオーソライザーの作成」を選択。自分が作成したユーザープールを指定する。

オーソライザーが作成できるとリソースの認証方法のCognitoが指定できるようになる。こんな感じ。

必須ではないと思うけど、「HTTPリクエストヘッダー」でAuthorizationを必須にしておくとよさそう。

ここまで設定したらAPIのデプロイを行なって変更点を反映する。

クライアント側の設定

クライアント側は簡単。localStorageから必要なtokenを取得してきて、リクエストヘッダーに付けるだけ。

idTokenを取得してAPI Gatewayを叩くクライアント側のコード

export default {
  props: ['example'],
  methods: {
    updateLabel(example, label) {
      let idToken = localStorage.getItem("CognitoIdentityServiceProvider.XXXXX.YYYYY.idToken");
      let headers = { headers: { 'Authorization': idToken } };
      axios.post(
        "https://ZZZZZ.execute-api.us-east-1.amazonaws.com/Prod/my_endpoint", 
        {
          url: example.Url,
          label: label,
        },
        headers
      ).then(response => {
        example.Label = label;
      }).catch(function (error) {
        alert(`Failed to annotate "${example.Title}"`);
      })
    },
  }
}

困ってるところ

めちゃくちゃ困ってるというわけでもないけど、しばらくLambdaを起動させていなかった後の初回起動に時間がかかる。VPC内にLambdaを作った関係でENI(Elastic Network Interface)を作成する必要があるからだそうな。回避策としては定期的にポーリングしておくとよいらしいが、そこまで困ってるわけでもないので困りだしたらやる。

参考

Pretraining Sentiment Classifiers with Unlabeled Dialog Dataを読んだ

論文読み会をやるので久しぶりに論文を読みました。久しぶりじゃダメなんだけど...。今年のACL2018でYahoo! JAPAN Researchの方が発表された内容です。

以下は雑なメモです。

問題点

  • 教師あり学習である程度いい精度を出すには教師データが10万件程度必要だけど、集めるのが大変
    • 3値分類、3人のアノテーターでmajority voting
    • 大体300人月
    • 24Kドル(240万くらいを高いと見るか安いと見るか)
    • 8時間 * 300日 * 時給1000円
    • もっとスキルのあるアノテータにやってもらうともっとかかる
    • アノテーション基準を勉強してもらう時間などは含んでいない
  • 実験データではラベル付きが8万件、ラベルなしが2000万件
    • 8万件じゃ精度はまだまだサチらない

提案法

  • 提案法は2段階
  • 1: pretrainingとして教師なしの対話データから(tweet-reply pairs)対話モデルを作る
    • encoder-decoder
  • 2: sentiment classifier(encoder-labeler)を教師ありデータを使って学習
    • 1で作ったencoder-decoderモデルのencoderパートを初期値として与え、pretrainingとして生かす
  • 言語モデルをベースにした既存手法もあるが、replyが付くようなtweetは感情に基づくことが多いのではないか?という仮説

モデル

  • dialog modelのベースラインはencoder-decoder
    • 入力は単語レベルじゃなくて文字レベル(絵文字とか顔文字とか多いからこっちのほうが性能高い)

比較した手法

  • 事前学習あり
    • Dial: 提案法(pretrainあり)
    • Lang: tweetのペアなしで言語モデルとして学習したLSTMをpretrainとして使う
    • SeqAE: sequence autoencoderをpretrainとして使う
    • Emo2M, Emo6M: この絵文字/顔文字が入っていたら正例/負例と見なすという一種のdistant supervisionで疑似的な学習データを使ったもの
  • 事前学習なし
    • Default: pretrainなし
    • LogReg, LinSVM: unigramとbigramのbag-of-words

実験結果

  • 学習データを5000件から8万件に変化させたときのF値の変化を見る
  • 学習データ量のサイズに関わらず、提案手法のDialが一貫して他の方法よりよい性能
  • Emo2Mは学習データにノイズが入るためDefaultより低い精度になっているっぽい
    • 例: うらやましいです。おめでとうございます orzがorzが入ってるので負例になってしまう
  • 古典的な機械学習のLogRegLinSVMはデータ量が少ないときにはDefaultよりもよい性能を出している
    • とはいえ、事前学習したDialLangのほうが性能が高い
  • 事前学習されたencoder-decoderの生成結果も感情に基づいている感じで、結果がよくなるのも納得

ゼロから作るDeep Learning ? ―自然言語処理編

ゼロから作るDeep Learning ? ―自然言語処理編