モニタリングに関するMachine Learning室の課題

まず1つ目の課題は、モニタリングコストの肥大化です。先ほどお伝えしたように、Machine Learning室では100を超えるMLプロダクトを開発、運用しており、それらが扱うデータソースもバラバラです。

Machine Learning室ではこれまで、内製ツールの開発などを通して、よりスピーディにMLプロダクトの開発ができるようになりましたが、運用するプロダクトが増えれば増えるほど、モニタリングのためのコストが肥大化していきました。

また、モニタリングやアラートの仕組みが、プロジェクトごとに独立して行われていることも多いです。しかし、それぞれが独立して作るモニタリングシステムは、最小限のものしかカバーしなかったり、アラートを上げるのみで、メトリクスを保存していかないなど、貧弱なものになりがちです。また、プロジェクトに強く依存することが多く、プロジェクトメンバーへの属人性が高くなりがちです。

このように、モニタリングが足りていなかったり、手が回らなくなったりしてしまうと、最終的に障害につながります。MLプロダクトで障害が起きる要因はさまざまで、例えばデータの欠損だったり、分布が変化したことだったり、モデルの更新などが考えられます。

しかし、数多くのプロダクトを運用する上で、毎日異常がないかを人間が見て回るのはとても大変ですし、イチから強力なモニタリング環境を整えることもまた大変です。ここでは、モニタリングに関して、実際に発生した障害や問題点をMachine Learning室での実例をもとに紹介します。

モニタリングで実際に発生した障害や問題点

まずは、データの欠損です。Machine Learning室では、典型的なバッチジョブは、外部組織から提供されているテーブルから、毎日最新のデータを読み出します。そしてそれをもとに、モデルの学習や予測を行い、予測結果を外部組織へ共有するという流れになっています。この時、提供されているテーブルになんらかの障害や遅延が起き、データの一部が欠損してしまうということがあります。

しかし、MLプロダクトの入出力をちゃんと監視していなかった場合、予測結果に異変が起きてもそれに気づけないということが起こり得ます。それによって、不適切な予測結果や空の予測結果を返してしまって、障害につながってしまうということが、まれに発生しています。

次に、モデルの更新についてです。あるプロダクトでは、毎日学習を行い、ユーザー属性を推定するタスクを行っています。ある時、予測精度を改善するため、モデルの構造を大きく変更するリリースを行いました。しかしこの時、新しいモデルと古いモデルで予測値の分布に大きな違いがあったことを見逃してしまいました。

たとえ全体の精度が高くなったとしても、分布が大きく変わってしまった場合、個々のユーザーの立場になると、自分の属性が突然大きく変化することになります。そうすると、提供されているコンテンツなどが大きく変化してしまうので、ユーザー体験という意味でも、サービスへ大きな影響がありました。

さらに、予測を安定化させるために、過去のモデルの予測値との多数決を取るような後処理を行っていました。そのため、この問題が発生するまでに、約2週間の遅延がありました。これによって、原因特定にも時間がかかり、また修復にも大きなコストがかかりました。日々のモデルの予測や、最終的な予測結果をちゃんとモニタリングしていれば、早期に対処できた問題だと考えています。

最後は、あるプロジェクトで自前のモニタリング環境を用意した時の話です。このプロジェクトでは、「Jupyter Notebook」を用いて、いくつかのメトリクスをチェックしたり、可視化を行ったりしていました。

このようなやり方は、融通を利かせやすいという点で便利ではありますが、いくつかの問題をはらんでいました。例えば、収集するメトリクスがその時必要だったものだけになってしまったり、アラートの条件が簡素なものに限られたりしていました。また、コードレビューが行われていないことも多く、根本的な信頼性にも欠けていました。

モニタリング環境を改善するために何が必要か

これらの経験を通して、モニタリング環境を改善するために、我々にはどのようなものが必要なのかを考えました。

まず、メトリクスの収集についてです。モニタリングする際は、何をどのように集計するかや、どこにメトリクスを格納するかという部分で、エンジニアはまず頭を悩ますことになります。

また、エンジニアの興味、関心がモニタリングに向いているということは、あまり多くはありません。そこで、メトリクス収集をできるだけ効率化させる仕組みが必要でした。具体的には、メトリクスを計算するためのツールセットと、メトリクスを保存できるストレージが必要だと考えました。

MLOpsにおけるメトリクス収集では、データの統計量やモデルの評価指標を集計する必要がありますが、それらは得てして面倒くさいものです。そこで、さまざまなメトリクスを集計するためのツールを用意することで、集計コストを下げられます。また、信頼性が高く簡単に利用可能なメトリクスストアを用意することで、モニタリングを導入するための障壁を、できる限り下げたいと考えました。

次に、大量のメトリクスを人間が見て回ることは難しいので、異常を検知して、ユーザーへ通知する仕組みが必要です。前半にお話ししたとおり、MLOpsにおけるメトリクスの異常は、トレンドや周期性を持っていることが多いです。そのため、柔軟な異常検知器と、検出した異常を通知するアラートシステムが必要になります。

最後に、収集したメトリクスを簡単に閲覧できる可視化システムが必要だと考えました。アラートで異常を検知するだけでなく、データやモデルがどのように変化しているのかを可視化することで、トレンドを把握したり、新たな改善アイデアを得ることが期待できます。

この時、メトリクスを見るのはエンジニアだけでなく、PMや事業部の担当者などもあり得ます。そのため、簡単に操作できるアプリケーションが必要だと考えました。これから紹介する「Lupus」は、このような目標を持って開発をスタートしました。

モニタリングシステム「Lupus」

それでは、前置きが長くなりましたが、我々が開発を進めているモニタリングシステム、Lupusについて紹介します。Lupusは、これまでお話ししたMachine Learning室のモニタリング課題を解決するために、現在開発を進めている、MLOpsのためのモニタリング共通基盤です。名前は英語で「おおかみ座」という意味です。

Lupusは、モニタリングにかかわるそれぞれの業種の負担を減らす、3つのコンセプトを持っています。まず、エンジニアが簡単にメトリクス収集を行えること。次に、運用者が簡単にプロダクトの異常を検知できること。最後に、プロジェクトメンバー全員が簡単にメトリクスやその異常の可視化を行えることです。

これらのコンセプトをもとに、Lupusは、サーバー、ライブラリ、SPAの3つの要素から成り立っています。

サーバーは、メトリクスを記録するAPIや、異常検知を実行してアラートを上げるサービスを提供します。また、認証やメタ情報の管理も行っています。

ライブラリは、汎用的なメトリクス収集のためのツールセットと、サーバー向けのAPIクライアントを提供するPythonライブラリです。

SPAは、記録されたメトリクスやアラート情報を探索的に可視化できるブラウザアプリケーションです。

Lupus全体のエコシステムは、このようになっています。ライブラリは、バッチジョブなど、メトリクス収集が行われる場所で使用されます。ただしライブラリは、必ずしも必須なわけではなく、REST APIを叩けるならどのような環境からでもLupusを利用できます。

また、集めたメトリクスに対しては、SPAを通してアクセスできます。Lupusサーバーはすべてのリクエストの受け口となっていて、適切な認証を行います。

例えばバッチジョブからメトリクスが送られてくる際は、トークンベースの認証を通して、メトリクスを受け取るかどうかを判断します。

一方ブラウザからのアクセスは、LINE社員のアカウントにひもづいて一人ひとりが識別されており、ユーザーは権限を持った情報にのみアクセスできるようになっています。

メトリクスの取り込みや異常検知の実行は、ジョブキューとワーカープロセスを組み合わせて、非同期かつ並列に処理されていきます。

具体的な処理の実行は、「Argo Workflows」を使って行われます。Lupusのワーカープロセスは、ワークフローの実行管理やメタデータを記録する役割のみを担っています。ジョブを並列実行する機構と、異常検知のような専門性が高く負荷も高いサービスロジックを分離することで、それぞれの開発管理をスムーズにしています。

メトリクスや異常情報のストレージとしては、「Elasticsearch」と「Hive」を用いています。Elasticsearchは可視化や検索のために用いていて、Hiveは長期的な保存のために用いています。

メトリクスの収集

それではここからは、メトリクス収集、異常検知、可視化の3つに分けて、Lupusが提供する機能とそのユースケースを紹介します。それではまず、メトリクスの収集についてお話しします。

前半で申し上げたとおり、MLOpsでは、データやモデルを監視する必要があります。入力データが変化するデータドリフト、目的変数が変化するコンセプトドリフトでは、それらの統計量を収集することで、一定水準の監視をすることが期待できます。

また、モデルの劣化や再学習などの変化に対処するため、モデルのパフォーマンスの監視も行う必要があります。

これについて、予測値の統計量を集めるほかに、真値を用いた性能評価やモデルの学習時のメトリクスなども有用です。

Lupusライブラリは、このようなメトリクスを計算するためのツールを提供しています。それでは、具体的な例をいくつか見ていきましょう。

まず、例えばLupusライブラリでは、統計量の計算をサポートする機能を提供しています。ここでは例として、左に示しているようなデータフレームを持っているとします。この時データが大量にあるため、「PySpark」を用いて分散処理しているとします。

各列からリージョンのようなグループ単位で、右のようなさまざまな統計量を収集したいというケースは、よくある話かと思います。しかしこれらを実際に書こうとすると、案外面倒だったりします。試しに自分で書いてみましたが、やはり長ったらしいコードができ上がってしまいました。また、列の型ごとに処理を考えないといけないのも面倒です。

例えばArray型の列からTop-kの情報を取り出したいとか、カテゴリカルな列のカテゴリごとに統計量を取りたいというような処理を書こうとすると、どうしても長ったらしくなってしまいます。レビューをする側も、こういうのを読みたいという人はあまりいないのではないかなと思います。僕は読みたくないです。

Lupusライブラリを使えば、このような集計処理を簡単に書くことができます。グルーピングする列と、列ごとに集計したいメトリクスを宣言的に書くだけで、メトリクスを計算します。これだけで、統計量のモニタリングを導入するハードルが一段下がるのではないでしょうか。

ライブラリでは、評価指標計算もサポートしています。例えば、多クラス分類問題では、左の表のように予測データと正解データのペアを渡すことで、精度やリコール、F1スコアなどを計算できます。

ほかにも、ラベルのカウントのような分布の情報も集計します。推薦問題向けの評価指標計算もサポートしています。特に推薦リストのうち、Top-kに関する評価指標計算も簡単に指定できるようになっています。

評価指標計算をサポートする利点は、ほかにもあります。それは、プロジェクト間などで指標を比較する場合の信頼性が上がることです。

見落とされがちですが、評価指標計算はその実装によって、細かな違いが生じます。特に、nDCGのような定義がバラバラだったりするものもあるので、評価指標でロジック間を比較する際は、計算するための実装も合わせないといけません。Lupusが提供している評価指標計算を多くのメンバーが利用することによって、過去に得られた結果などと比較する際に、より信憑性が生まれます。

そのほか、「MLflow」に記録されたメトリクスを取得できる機能もあります。Machine Learning室では、学習管理にMLflowを活用しています。MLflowに記録されたさまざまなメトリクスをLupusに流すことで、そのほかのメトリクスと一緒に共通的に参照できます。

Lupusを利用したメトリクス収集

それでは次は、Lupusを利用したメトリクス収集の流れを紹介します。メトリクス収集をする時の流れは、このようになります。まずユーザーは、収集したいメトリクスを計算します。このステップは、先ほど紹介したように、Lupusライブラリを使うことで効率的に行えます。もちろん、独自にメトリクスを計算してもかまいません。

次に、Lupusサーバーへ収集したメトリクスを送信します。ライブラリには、API向けのクライアントクラスも実装されているため、簡単に送信できます。ユーザーが行う作業は、たったこれだけです。コードにすれば10行程度で終わる場合もあります。

送信したあとの裏側も、紹介します。Lupusサーバーは、メトリクスを受け取ると、S3互換のオブジェクトストレージに、一時的にデータを配置します。

その後、メトリクスを記録するジョブを、ジョブキューへと登録します。

ワーカープロセスは、ジョブキューからジョブを取得し、Argo Workflowsで処理を実行していきます。ワークフローでは、ストレージからメトリクスを取得したあと、データのバリデーションを行います。

正常な形式のデータであることが確認できたら、HiveとElasticsearchへメトリクス情報を書き出します。以上が、ユーザーが収集したメトリクスが永続化されるまでのフローになります。

メトリクスに対する異常検知

次に、メトリクスに対する異常検知について説明します。何度か申し上げているとおり、MLOpsにおける異常は、ハイコンテクストな要素をはらみます。基本的なアラートシステムでは、左側にあるように、閾値を超えたらアラートを上げるというものや、直近の値と比べて大きく変化があったらアラートを上げるというものが一般的です。

一方、MLOpsでは、メトリクスが周期性やトレンドを持つことも少なくありません。そのため、右側にあるように、周期的な変化から大きく逸脱したことや、トレンドが変化したことを検出できるのが望ましいです。この問題へアプローチするために、Lupusには機械学習を活用した高度な異常検知が実装されています。

Lupusには現在、4種類の異常検知ロジックが実装されています。基本的な異常検知としては、閾値ベースのものだったり、直近の値をベースラインとするウィンドウベースのものが実装されています。

これに加えて、時系列予測を活用した高度なものとして、「Prophet」を利用した異常検知が実装されています。過去のデータからProphetを用いて時系列予測を行い、その信頼区間をもとに異常かどうかを判断しています。そのほか、Twitter社がOSSとして公開しているライブラリを用いた異常検知も実装されています。

これらは、すべて自分たちで用意したものなので、今後も自分たちの要件に基づいて、継続的にロジックが追加されていく予定です。

Lupusを利用した異常検知

異常検知する流れも紹介します。異常検知の実行は、ユーザーのリクエストが起点になっています。ユーザーは、異常検知するメトリクスの範囲と異常検知器のパラメータを指定します。

Lupusライブラリには、このためのインターフェイスやクライアントが実装されているので、ユーザーは直感的にリクエストできます。リクエストはジョブキューに登録され、順次処理されていきます。

異常検知のワークフローは、まず、ユーザーが指定したメトリクスをHiveから読み出します。そして、それぞれのメトリクス系列に対して異常検知を実行していきます。

この時、扱う系列の数が多かった場合、一つひとつに時系列予測のような比較的重たい処理をすると、計算時間がとても長くなってしまいます。そこで、Kubernetesを活用して、適宜分散処理をするようになっています。異常検知の結果は、HiveやElasticsearchに保存されたのち、ユーザーへ通知されます。

「Lupus SPA」による可視化

最後に、「Lupus SPA」による可視化部分について説明します。Lupus SPAは、収集したメトリクスやアラートを可視化するための、シングルページアプリケーションです。

表示する情報は、Elasticsearchに保存されていて、サーバー経由で取得されます。Lupusサーバーではユーザー認証があり、誰がどのメトリクスを見てもいいが管理されているので、正しい権限を持つユーザーのみが情報を見られるような設計になっています。ユーザーは、Lupus SPAを通してメトリクスを可視化したり、チャートの検索をしたり、またカスタマイズ可能なダッシュボードを利用できます。

しかしこのようなツールは、世の中にすでに数多く存在しているんじゃないかと思われるかもしれません。実際のところ、Elasticsearchには「Kibana」という可視化ツールがあり、Lupusの開発時には、Kibanaでデータを見たりもしています。では、なぜ自分たちでイチから作ったのでしょうか? これにはいくつか理由があります。

まず、我々の用途に最適化されたシンプルなUIを提供したいからです。世の中にはすばらしいOSSがたくさんありますが、それらはかなり汎用的に作られています。そのため、例えば、学習コストの高いクエリ言語が必要だったりもします。Lupusは、共通化されたスキーマを持っていますし、ユースケースにフォーカスしたシンプルなUIを実現したいと考えました。

また、Lupus特有の要件もあります。例えば、異常検知情報の表示や、メトリクスの絞り込みなどがあります。これに加えて、高いセキュリティレベルを保つために、複雑な認証機構も必要です。これらを総合すると、自作することがベストだと考えました。

Lupus SPAの各画面

ここからは、Lupus SPAの各画面をサンプルデータとともに紹介していきます。これがまずトップ画面です。左側からは、ダッシュボード一覧へ移動できます。右側からは、データソース一覧へ飛び、さまざまなチャートを探索できます。Lupusでは、データソースという単位でメトリクスを区分けしていて、ユーザーは、データソースを作成した上で、そこへメトリクスを流し込んでいきます。

次に、チャートの探索画面です。ここでは、これまでに収集したメトリクスのチャートを探索できます。このサンプル画面では、job.service1-recommendというデータソースで収集している情報を表示しています。

少し文字が小さいですが、例えば、左上のチャートでは、このサービスでのレコメンドに関するnDCGの値の推移が表示されています。ほかにも、リコールやレコメンドの多様性の指標の1つであるエントロピーが収集されていて、この画面では、このデータソースに関連するすべての指標を一覧して見ることができています。

チャートをクリックすると、チャートの詳細画面へ遷移します。ここでは、表示するメトリクスの範囲を絞ったりできます。画面には、リージョンごとのnDCGのチャートが表示されています。異常検知を行った際に、異常が発見された際には、そのようなアノテーションがつきます。

各メトリクス系列には、ユーザーが任意に指定できるグループ分けの情報があり、UI上でそれらを自由にフィルターして、必要な情報だけを見ることができます。

さらに、メトリクス系列の1つをクリックすることで、異常値情報の詳細な表示も行えます。例えば異常検知のロジックによっては、信頼区間だったり予想される系列などが存在するので、そのようなメタ情報を視覚的に確認できます。

こちらはダッシュボード画面です。ユーザーは、チャートを選択してダッシュボードを作ることができ、必要な情報へすばやくアクセスできます。この画面では、Service1というサービスに関する情報が集約されていることがわかります。

以上で、Lupusが持つ機能についての紹介は終わりになります。

Lupusを導入したことで改善された点

最後に、Lupusはまだ開発段階ですが、Lupusを導入したことで改善された点を、いくつか紹介します。

まず、Lupusを導入したことで、日々のメトリクス収集が簡単になりました。また、Lupusを通して、今まで気づいていなかった障害のきっかけに気づくことができました。また、毎日収集を行い可視化することで、今まで知らなかった精度の変化に気づくということもありました。それによって、プロダクト改善へのモチベーションが高まりました。

今までレビューのなかったモニタリングのnotebookを廃止して、Lupusを使ったバッチジョブに置き換えることで、コードレビューが入り、信頼性が高まりました。

また、今まで必要な時に長い時間をかけてアドホックに集計していたものを、Lupusを使って毎日集計するようになったことで、細かい頻度でメトリクスが収集され、また、すぐに最新の情報を見られるようになりました。また、SPAを使って、メンバーへの共有も簡単になりました。

まとめ

それでは、本発表のまとめです。まず冒頭では、MLOpsにおけるモニタリングの紹介をし、一般的なDevOpsにはない要件があることを説明しました。

次に、LINEのMachine Learning室が抱える課題を紹介しました。それは、非常に多くのプロダクトを抱える中で、それぞれにしっかりしたモニタリング環境を整備するということがとても難しいというものでした。

最後に、この課題を解消するために開発されているLupusについて紹介しました。Lupusは、エンジニアや運用者、PMなどさまざまな立場の人間が、苦労せずにモニタリングを行える環境を目指しています。まだ開発段階ですが、実験的な導入でも大きなインパクトがあったことも紹介しました。

紹介した我々の実体験やソリューションが、みなさまのモニタリング環境の改善のヒントになれば幸いです。

こちらが、Lupusの開発や本発表をする上で参照した書籍やWebサイトになります。発表は以上になります、ありがとうございました。