この記事は,SYSKEN Advent Calendar 2018の16日目の記事です.
RSです.少しぶりですね.
先日投稿したエントリとは打って変わって今回はデータ分析・機械学習系のお話です.皆さんは,昨年私が投稿した当部活の鍵通知システムをご存知でしょうか.
あのシステムでは,当部活で使用している活動場所の鍵が開いたことと閉まったことを取得できます.
このデータ,収集して分析すると部員の行動パターンがわかって面白そうではありませんか?
そんなことを思ってせっせと運用を続けて1年が経ったので,ちょっと分析してみた話をします.
はじめに
鍵管理ハードウェアから得たデータは,Herokuでホスティングしているサーバを通してHeroku Postgresに収集するようにしています.
これまでは,これをもとに鍵情報のダッシュボードを運用していました.
今回は,収集したデータをCSV形式で取得してJupyter Lab上で分析を実施します.
データセット
分析・学習タスクの検討
まず分析,予測したいなと思うにあたって何をするのが面白いか考えてみました.
鍵通知システムは,当部活のSlackに通知を飛ばすのですがここで部員たちの面白い現象が見られました.
ここで,key-notify botの通知している時刻とついているリアクションがミソです.どうやら部員たちは遅い時間とか休日などの普段部活動が実施されないときの通知にリアクションをつける傾向があるようです.
ここから思いついたのが,この部員たちのリアクションデータと合わせて異常な活動を検知することができないかということです.
このタスクを設定して分析・学習をおこなっていくこととします.
データセットの分析
学習の前段階として,データを分析することにしました.とはいっても,簡単にヒストグラムを作成してみる程度です.
テーブルデータの取扱いには,便利なPandasを使います.
今回のデータセットは,下記の要素が格納されています.ただし,部員のリアクションは今回は面倒なので人手で収集しました.
- 鍵の通知を送信したタイムスタンプ
- 鍵の状態(鍵が置かれた/鍵が取られた)
- 部員のリアクションがあるか
分析するにあたって,まず目をつけたのが部活動の開始・終了時刻です.これの状態がわかれば時刻が特徴量としてふさわしいとしてアタリを付けることができそうです.
具体的には,時間-頻度のヒストグラムを出してみました.
概形だけを見ても,アヤシイ感じが伝わってきますね.
(特に,右側(鍵が取られた)のヒストグラムのうち,横軸0周辺にデータが有るのはなぜなんでしょうか…)
さて,ここから結構カン頼りなのですが当部活では今年高専プロコンに参加しており,このときハードワークだったような印象があります.
本校の夏休みは,8/10からで高専プロコンの当日は10/25からです.では,8月から10月までのデータとそれ以外のデータに分割してヒストグラムを比較するとどうなるでしょうか.
このとき,ヒストグラムは面積1で標準化しています.
水色が平常時,黄色が8月-10月を示しています.
見事に分散が違いますね.仮説通りでした.特に印象的なのは,右側の黄色のグラフのピークは2, 3点あるようにも見えるあたりでしょうか.
結論は,時刻のデータと月のデータは有益な特徴量ではないかなという感覚がつかめました.
ただし,部員のリアクションはこれとしっかり結びついているかどうかはまだわからないという感じです.
学習
さて,先程の分析はしっかりしたものではありませんがとにかく手を動かすことでより多くの事実を見つけるのが先決だろうと思い学習に入ることにしました.
今回の目標は,部員がリアクションするであろうデータを予測することです.
まず,部員はよく現れないデータを異常として認識してリアクションしているであろうという仮説を立てました.
学習手法でまず思いつくのが部員たちのリアクションをアノテーションとして分類問題を解く手法です.
しかし,よく現れないデータをもとに分類器を学習させるとデータセットの歪みの影響を受けて正しい分類ができないであろうことが想定されます.
具体的には,単純に正解率だけを見れば高いけども実際はどんなデータでも異常でないと分類してしまっている恐れがあります.
これ以上の解説は,機械学習入門エントリっぽくなるので回避します.
よって,今回は異常検知のアプローチで予測することができないかを検討しました.
-meansによるクラスタリングでのアプローチ
異常検知というといろいろな手法があります.私も異常検知の領域は詳しく取り組んだことがないのでいろいろわかるわけではありませんが,いろんな手法があるらしいです.
-meansとは,クラスタリング手法の1つです.データセットに個のクラスタがあることを仮定してそれぞれの特徴量でクラスタの中心を逐次計算して更新していくことでクラスタを求める手法です.
これまた詳しく説明しすぎるとなんのエントリかわからなくなるので回避しましょう.
では,クラスタリングするだけで異常検知できるのかと言われるとそうではありません.要は,クラスタのうちどっちが異常なクラスタであるかを決定しない限りはわからないのです.
そこで,今回は異常なクラスタはその要素数が小さいという仮定をおいて予測してみることにしました.
-meansの動作はscikit-learnでおこなうこととします.
scikit-learnを使う機会はあまりなかったのですが,すごく便利ですね.自分で実装いらずですよ.
実際には,鍵の状態がON/OFFのときそれぞれでデータを分けてクラスタリングしています.
鍵のON/OFFのデータできれいにクラスタリングされてもなんの意味もないですからね.
要素に大きな意味はありませんが,週-時間の要素での散布図がON/OFFでこんな感じになりました.
また,評価値として正解率を導入すると前述の通り問題があるのでF値を導入します.
F値によって,正解率が高いかだけでなく誤り率が少ないかどうかも評価されます.
ちなみにF値は以下の範囲を取ります.
詳しくはこちらとかが良いかもしれません.
f1 score (ON): 0.13043478260869565 f1 score (OFF): 0.0784313725490196
クラスタごとの要素数を見ると,ONのときはそれぞれ, でした.
また,ONのときのリアクションがついていないデータとついたデータはそれぞれ, です.
あまり良くないですね.
異常値のクラスタができるという仮説はちょっとオーバーだったようです.
LOFによる異常検知でのアプローチ
では,他の異常検知手法で取り組んでみましょう.
LOF(Local Outlier Factor)は,異常検知手法の1つです.
ざっくりいうと,データの分布から集合からはずれた値を検出する手法です.LOFの面白い点は,データ全体の集合とそこからの外れ値を検出するのではなくデータの局所的な密度の異なる集合ごとにそこからの外れ値を検出する手法であるという点です.
もう少し理解を得たい場合は,以下のスライドなどを見てください.
局所的に外れ値を検知するならばON/OFFのデータをあわせてみても似たような結論が得られるのかもしれません.
ただ,まずはON/OFFそれぞれで検出してみました.
先程と同じく,週-時間の要素での散布図がON/OFFでこんな感じになりました.
F値はこんな感じです.
f1 score (ON): 0.35294117647058826 f1 score (OFF): 0.18181818181818185
さっきよりは改善した感じですね.
ただ,OFFのときのF値がなかなか低い点が気になります.
ここで,異常と予測されたデータと実際に部員がリアクションしたデータを比較してみましょう.
異常と予測したデータ
month day week hour 0 8 20 0 7 1 8 22 2 7 2 8 23 3 8 3 8 31 4 0 4 9 14 4 10 5 9 15 5 11 6 10 30 1 23 7 10 31 2 23 8 11 1 3 10
リアクションしたデータ
month day week hour 79 8 21 1 21 83 8 22 2 22 91 8 31 4 0 129 10 5 4 20 143 10 16 1 22 145 10 17 2 22 149 10 18 3 20 155 10 19 4 22 159 10 23 1 22 165 10 26 4 22 167 10 29 0 22 171 10 31 2 23 175 11 1 3 22
ここからわかる大変面白い事実は,データ的には残業はもはや異常値ですら無いということです.
具体的に見ていくと,予測したデータは早朝の時刻を主に異常値として検出しています.確かに,部活動は基本的に午後から開始されるので異常と言えます.
一方,部員がリアクションしたデータは基本的に夜遅い時刻です.22-0時が多いでしょうか.
実は,部員がリアクションしたデータは異常検知の結果などではなく残業を警告していたようなのです.
(厳密に言うと,データの分布は基本的に午後が多いのでそれも要因であると推測されます.ちょっとオーバーに言っているかもしれません.)
これには実験していた私もびっくり仰天してしまいました.
では,この問題の回避方法です.
現在は,scikit-learnのLOFへの学習用データとしてON/OFFのすべてのデータを入れています.
これを,部員がリアクションしたデータのみ除去して学習させてみます.
すると,F値はそれぞれ以下のようになりました.
f1 score (ON): 0.3157894736842105 f1 score (OFF): 0.625
OFFのときのF値がかなり上昇しましたね.
それもそのはず,おそらく残業データを除去して学習させているので残業していないデータの分布から外れた残業データはきちんと異常として検知されるはずです.
学習用データを事前にこのように処理していなかったせいとはいえ,実験中に大変興味深い結論を見ることになってしまいした.
よくよく考えれば,データ分析の際にOFFのヒストグラムが分散が大きいことから予測できた事態ではありますが,実験中は思わぬ結果に笑ってしまいました.
考察と感想
部員は,よく現れないデータを異常として認識してリアクションしているであろうという仮説を立てました.
この仮説が誤っている事が実験中にわかりました.
よって,新たな仮説を立てて実験手法を検討する必要があると感じています.
まだまだ面白い点が尽きないなと感じました.
今後は,部員のリアクションデータを収集する方法を検討したり,予測結果から部員に異常を通知したりすることができるのではないかと考えています.
ほかにも,異常を検知するというアプローチだけではなく様々な分析や予測ができるのではないでしょうか.
手法の紹介中になにか誤って説明している点などありましたら,コメント等でご連絡頂けると幸いです.
実験したNotebook
参考
- Smtp (KDDCUP99) dataset – ODDS
- sklearn.cluster.KMeans — scikit-learn 0.20.1 documentation
- QCon2014:異常検知 機械学習 – Qiita
- sklearn.neighbors.LocalOutlierFactor — scikit-learn 0.20.1 documentation
- sklearn.metrics.f1_score — scikit-learn 0.20.1 documentation
追記
そういえばテストデータでのチェックを厳密にやっていなかったですね.汎化性能の検証をしていないとはなんとも言えませんね…
気が向いたら引き続き挑戦しようかなと思います.
コメントを残す