SREチームとR&Dチームを兼務している@taxagawaです。最近ではGoで一部マイクロサービスの開発も行っています。
今回の投稿ではタイトルにもある通り、2021年9月現在リリースに向けて絶賛開発中の異常検知サービスについて、その一部をご紹介したいと思います。
弊社が提供するスマートマットは、スマートマット上に載せたモノの重量を定期的に計測することで無人の在庫管理や自動発注を実現するIoT製品です。それゆえ、多くのお客様にスマートマットをご利用いただく中で各在庫ごとの膨大なデータが計測のたびに日々蓄積されています。プロダクトが徐々に成熟してきている現在、この貴重なデータを生かした高度な機能を開発し、お客様へ新しい価値を提供することが求められる段階となっています。
蓄積されたデータを利用したものとしては既に最適発注点予測機能が管理コンソール上にて提供されていますが、それに続くものとして現在異常検知機能の実装が進められています。現時点で実装を予定しているスマートマットの異常検知機能としては、以下の3つを想定しています。
それぞれ名前の通りの機能ではありますが簡単に説明すると、まず過剰消費検知は通常時よりも在庫の消費量が多い場合アラートを行うもので、たとえば盗難の検知や消費スピードの急激な増加による欠品の防止などに利用することが想定されます。未消費検知は一定期間消費が発生していない在庫に対してのアラートで、滞留在庫の存在を検知し、在庫ロスの削減やキャッシュフローの改善に繋げてゆくことを想定できます。過少消費検知は未消費というわけではないが、通常時よりも消費が少ない在庫に対するアラートで、具体的には毎日一定量を消費しなければならない在庫(スーパーにおける生鮮食品など)の消費量が減少した際に鋭敏に反応できるような使用法が想定されています。
開発状況としては過剰消費検知と未消費検知についてはアルゴリズムが完成し、α版提供のためのフロント・バックエンド開発が進行中です。(2021年9月時点)過少消費検知に関してはまだアルゴリズム開発の段階となっています。本記事ではこのうち過剰消費検知に関して、その仕様を解説したいと思います。
過剰消費検知に使用するデータとしては次のものがあります。
在庫ごとの計測頻度は最小で1日1回、最大で1日1440回となっています。重量データはグラム単位の0以上の整数で、計測時刻と合わせて蓄積された過去の履歴を使用します。連続する2つの重量データの差分をとることで、その計測の間における在庫の消費量が計算できます。ある在庫における具体的な消費量の時系列グラフは下記のようになります。
まずアルゴリズムの選定理由ですが、なるべくスモールに開発を始めたいということと、自分自身が機械学習系の開発の初心者であるということから、なるべく簡潔である程度の結果が得られるアルゴリズムを採用する方針となりました。そもそも異常検知に関する知識が少なすぎたため基本的な調査から始めたのですが、まずは今回のデータセットにある程度適用できそうで且つ比較的実装が楽そうなホテリング理論を応用する形をとることになりました。
ホテリング理論に関する詳しい説明はググればたくさんでてくるので割愛しますが、簡潔に述べるとデータセットが正規分布に従うという前提のもとで、平均や分散といった基本的な統計量と観測値から算出した異常度を用いて外れ値を検出する手法です。前提の部分ですが、それなりの期間以上スマートマットで運用されている在庫であれば定期的に一定に近い量の消費が発生するため、その一定の消費間隔でリサンプリングすることで正規分布に従うデータセットが得られるのではと考えました。
スマートマットによる重量の計測では、実際の重量と計測されデータベースに記録される重量に誤差が見られます。こちらにあるようにスマートマットのサイズによって最大計測誤差は異なっており、過剰消費検知においてもこの誤差による影響を計算前に取り除く必要があります。実際の処理としては、ある在庫はその重量に対して適切なマットのサイズを利用していると仮定して、最大計測誤差以上の消費が発生しない限りは消費量がゼロであると見なすことで誤差の除去を行っています。
長くスマートマットを利用している場合、1回の棚卸しで消費する在庫量や在庫消費の頻度が変化することはよくあることだと考えられます。このように在庫の扱いが常に変化しうることを考慮し、平均と分散の計算には指数平滑移動平均 (pandas.DataFrame.ewm) を使用しています。ewm関数には数種類のパラメータを渡すことができますが、半減期を指定するhalflifeを使用しています。推論時のhalflifeの値は、学習用の重量データに対して事前に正常か異常かの簡易的なラベル付けを行い、最適な結果が得られた際の値を利用します。
以下に異常と判定された結果と判定されなかった結果をそれぞれ1つずつ示します。個人的に前者はうまく判定ができていると思いますが、後者は判定結果は正しいがまだ改良が必要だと思っています。ホテリング理論によると計算された異常度は自由度1のカイ二乗分布に従いますが、今回は全体の5%が異常と判定されるような閾値を判定に用いました。(具体的な数値としては5.024となります)それぞれの結果について、1枚目がデータ利用の対象とした全期間の消費量、2枚目が直近5か月分の消費量を表すグラフになります。縦軸の単位はグラムで、横軸が計測日になります。また、グラフ上の一番右に来ている消費が異常検知の対象となる消費量になります。
異常と判定されたもの
こちらのデータでは全体的に見ると検知対象のデータはそこまで大きな消費量には見えませんが、過去のデータほど判定に与える影響が小さくなることを考慮して直近の値だけを見ると、確かに通常の消費と見られる山(1,000~2,000の値を取る部分)と比較して2倍以上の消費が行われていることがわかります。このときの異常度は42.55と閾値に対して比較的大きな値を取りましたが、少なくとも異常に消費が発生しているのではないかと警告するには十分な状態であると見て取ることができます。
異常と判定されなかったもの
こちらのデータの異常度は4.97でかなり閾値に近い値となっています。判定自体は正しいと思いますが、本来であればもう少し異常度が低くなっても良いかなと思います。高い異常度が出た要因としては、この在庫の消費スピードが比較的最近変化したことと、検知対象データの数日前に明らかな異常があり平均や分散がそれに釣られて大きくなったことが考えられます。いずれも判定結果を歪める要因として事前に考えられるものであり、対応するための処理も導入していましたが、まだまだ改良の余地がある結果となっています。改善案として例えば、データがより正規分布に近づくように何らかの変換を施したり、消費スピードの変化を検知し変化前の消費による影響を今よりもさらに小さくしたりするような処理の導入が考えられます。
前項のアルゴリズムにより過剰消費の検知はある程度可能となりました。しかしこの実装はあくまでα版のものであり、すでにいくつかの改善案が挙げられています。ここではそのうち大きな改善となると考えている2つをご紹介します。
現時点での仕様ではすべての在庫において、異常スコアに対する判定基準値が同一の値となっています。理想としては各在庫ごとに個別の判定基準値を設定するべきであり、さらにその値も動的に変更されるべきですがそこまでの実装には至っていません。この問題を解決するためには、やはり実際にご利用していただくお客様自身からフィードバックをいただき、それを取り入れることが考えられます。利用開始時点では初期の基準値を使用し、ある程度運用された段階でお客様自身の目で異常判定の結果に問題がないかをご確認いただき、現状維持か判定を厳しく/緩めるのいずれかのフィードバックをしていただくイメージです。
今のところ異常判定は1日1回稼働するバッチによって前日分に対してまとめて行われています。しかし、実際の計測は1日に複数回行われることがほとんどであるため、1日1回のバッチでは少しリアルタイム性に欠けてしまいます。そこで今後は各在庫で計測が行われるたびに異常判定の結果が出力されるような機能変更を実装していく予定です。想定ではバッチ処理による結果の一括出力から、SageMakerの推論エンドポイントを利用した形式に変更していく予定です。コード及びインフラに大幅な改修が必要となる変更ですが、特に盗難などのケースではリアルタイム性が重要であるため、その点をより追求した機能改修は必須のものかと思います。
本記事では私が現在開発に全面的に携わっている異常検知機能について、その一部である過剰消費検知機能の仕様を解説しました。そこまで複雑なアルゴリズムではありませんが、テストデータに対する結果は中々満足のゆく結果であるため、とりあえずはα版としてお客様へと提供可能な状態にはできたと自負しています。もちろん前項でも挙げたように、精度の向上やさらに便利な機能へと進化させるためにやるべきことは山積みとなっています。100%の精度を提供することは難しいかと思いますが、少しでも多くのお客様にご利用いただき、満足してもらえる機能となるよう引続き精進していきたいと思います。
今回は過剰消費検知の解説だけでしたが、次回は未消費検知や過少消費検知、あるいはインフラに関する内容をお届けできればと思います。