Machine Learning Casual Talks #10でMackerelのロール内異常検知について発表しました

メルカリさんのオフィスで開かれたMachine Learning Casual Talks (MLCT) #10に「教師なし学習によるMackerelの異常検知機能について 〜設計/運用/評価の観点から〜」というタイトルで登壇してきました。

MLCTは機械学習をサービスで運用していく知見を共有する勉強会です。YouTube等で動画配信を積極的にしてくださっていて、はてなの京都オフィスでも鑑賞会と称してランチタイムに同僚と発表を見させてもらっていました。普段から勉強させてもあっていた勉強会に、登壇という形でちょっとはお返しできているとうれしいです。登壇させて頂き、ありがとうございました!

私の発表資料はこちらです。スライド46枚ありますが、発表は15分だったので本番はこれの短縮バージョンで発表させてもらいました。

f:id:syou6162:20190530065428j:plain

‪@chezou‬さんのこういうTweetもありましたが、開発者の僕自身結構胸熱(?)で、発表の見所としてはMackerelというプロダクトの概念にフィットするものを、運用が大変にならないようにいかにシンプルに作るか、というところです。具体的にはこういう感じ。

  • どの単位で異常検知モデルを作るか
    • 全体で一つだと大きすぎる、ホストに一つだと細かすぎる
    • Mackerelというサービスの根幹にある「ロール」という概念をうまく利用する
      • ロールは例えば、はてなブログのDBサーバーというようなグルーピングのことです
    • 異常検知のために特別な設定をしてもらうというより、Mackerelの概念に沿って使ってもらえば、異常検知も自然とうまく動作するような設計を目指した
    • プロダクトが先、機械学習が後
  • ホストを異常と判定したときに、その根拠をどうやって提示するか
    • 世の中的には、ブラックボックスのモデルを人間が解釈可能な近似したモデルを用意する(例: LIME)のが主流になりつつある
    • しかし、運用観点では管理しないといけない機械学習モデルが増えて厳しい。予測のlatencyの増加という観点からも望ましくない
    • 異常判定に使っている混合ガウス分布の条件付き確率分布を計算し、どのメトリックが異常かを判定している
      • 混合ガウス分布は性質のよい確率モデルがベースにあるので、条件付き確率も閉じた形で書き表わせる
      • 根拠提示用にもう一つモデルを用意する必要がない
    • 混合ガウスを使っていたとしても、混合数が大きかったり次元数が高いとlatencyなどの観点では厳しいが、ここでも「ロールに対してモデルを作る」ということが効いてくる
      • ロール内では状態数はそこまで多くならないので、混合数はそこまで大きくする必要がない
      • 判定に必要な次元数を抑えるため、ドメインエキスパート(つまり監視のプロである社内のSRE)にどの特徴量は削っても監視には影響なさそうかヒアリングしにいった
  • 誤検知を防ぎたいが、教師なしであるモデルの挙動を人間の直感とどう合わせるか
    • ほぼ動きがないメトリックがほんの少し動いた場合、モデルは異常と判定するが人間はそれを誤報と感じる
    • なるべく人間の直感に合うように振る舞わせたいが、教師ラベルを整備して制御といったことは教師なしではできない...
    • 混合ガウス分布を選択したことがここで生きる
    • 混合ガウス分布は由緒正しき(?)確率的生成モデルであるので、分散の事前分布に「memoryで全体のX%くらい変動するのはまあありえるよね」といった人間のドメイン知識を埋め込む
  • 継続的な改善
    • 自動で取れる数値はダッシュボードにまとめているが、手動でのアノテーションでのシステム評価にそれなりに力を入れている
    • ほぼ全ての異常検知によるアラートに目を通し、正しいアラートか誤報かのアノテーションをしている
      • 誤報率を時系列でトラッキング
    • 誤報の中でカバレッジの大きいものに関しては改善の施策を実施、施策の実施後には再度アノテーションをして実際に改善しているか確認、というのをひたすら繰り返している

発表に対する反応

合わせて読みたい

超交流会に登壇しました

ハカルスの染田さんと同僚のid:daiksyさんと一緒に登壇(パネルディスカッション)してきました。アルゴリズムがどうこうというより、実際にユーザに使ってもらえるプロダクト・サービスにするのはどうすればよいか、それを運用していくために必要な苦労・工夫などざっくばらんに話しました。立ち見(座り見?)が出たり、パネル後も質問してくださる方がいらっしゃるなど、大変盛り上がってよかったです。

アジェンダとしては以下のものを用意していたのですが、時間内ではとても語り切れなかったので、続きはMACHINE LEARNING Meetup KANSAI #5で!!!

  • 技術的話題
    • 機械学習の精度どこまでやる?
    • ノートブックのレビューってどうしてる?
    • 精度 vs 解釈性
  • 運用的話題
    • アプリケーションチームとの境界、役割分担
    • 引き継ぎ/ドキュメント化
    • アノテーションのルールの運用
  • ビジネス的話題
    • リリース日どう決めてる?
    • コストと儲かり

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に任せることができるので気楽。