Fire Engine

消防士→ITエンジニア→研究者

消防士を辞めて1年2ヶ月…GMOペパボのインフラエンジニアになった

2018年2月1日にGMOペパボ株式会社に入社しました!
消防士を辞めてエンジニアに転職してからの1年2ヶ月は福岡のシステム開発会社で機械学習などのデータサイエンスの分野に取り組んでいましたが、ペパボにはインフラエンジニアとして入社しました。今回の記事は、いわゆる転職エントリというやつです!転職の理由などを振り返っていきます!
消防士からエンジニアへの転職については下の記事にまとめてます。

blog.tsurubee.tech

なぜインフラエンジニアになったのか

私はこれまでデータサイエンスの分野に取り組んできたため、「機械学習エンジニア」や「データサイエンティスト」といった道もあったのですが、インフラエンジニアになりました。その理由を書いていきます。
データサイエンスの分野は、機械学習アルゴリズムへの理解や、それらをプログラムに落とす力はもちろんのこと、それに加えて対象となるドメイン(事業領域)の知識が重要となります。私はこのドメインが自分の興味の領域と一致していると、データサイエンスをやっていて一番楽しいと感じることができると気づきました。逆にあまり興味がないデータを扱うのはそんなにおもしろくないなとも思いました。そう感じて以来ずっと、自分が興味があって、データサイエンスの活用が有効である領域を探していました。
そんなときにたまたま参加したイベントでペパボの@udzuraさんの発表を聞き、インフラに興味を持ちました。ITインフラの現場においては、サーバのCPUやメモリ等の各種リソースの使用量、ネットワークトラフィック、I/O要求といった時系列データが時々刻々と蓄積されていて、データサイエンスの応用の幅が広いと感じました。(その時のスライドは下のリンク)

コンテナたちを計測すること ~ ロリポップ!マネージドクラウドの裏側 ~ /wip-how-to-get-container-metrics - Speaker Deck

これをきっかけにインフラに興味を持ち、インフラエンジニアへのジョブチェンジを決めました。きっかけはデータサイエンスの活用の場としてのインフラでしたが、今ではデータサイエンス抜きにしても、純粋にインフラに興味を持っており、Linuxカーネルやネットワークまわりなど学びたいことが増えました。

なぜペパボに入ったのか

上でも述べましたが、ペパボの方の発表がきっかけでインフラに興味を持ちました。さらにペパボにはペパボ研究所という事業を差別化するための研究開発に取り組む組織があり、そこの研究実績を見ていくと、ITインフラの抱える課題をデータサイエンスの力で解決していこうとする事例をいくつか見つけて、ひたすら興味が湧いてきました。

特徴量抽出と変化点検出に基づくWebサーバの高集積マルチテナント方式におけるリソースの自律制御アーキテクチャ / 2017 iot36 - Speaker Deck

アクセス頻度予測に基づく仮想サーバの計画的オートスケーリング/Scheduled Autoscaling of Virtual Servers by Access Frequency Prediction - Speaker Deck

それからずっとペパボのことが気になっていて、ネットで情報を追いかけていました。そんな矢先、ペパボカレッジの応募を見て、これしかない!と思いました。

ペパボカレッジ

ペパボカレッジとは第二新卒エンジニア向け研修のことで、ペパボカレッジで採用されると、中途採用でもみっちり研修を受けられます。私はエンジニア歴も浅いし、そもそもインフラの経験がなかったため、このペパボカレッジの応募はまたとないチャンスだと感じました。
ペパボカレッジについては詳しく知りたい方は下の記事を見てください!

www.wantedly.com

私はペパボカレッジ6期生として入社して、3月1日から東京で研修を受けている真っ最中です。素晴らしい講師陣と熱い同期に恵まれて、やっていくぞ!という気持ちが高まりまくっています!
インフラエンジニアのペパボカレッジは今回が初めてなので、4月上旬に研修が終わった後には、全体のまとめを書きたいと思います!多分書きます。

転職するときにやっておいてよかったこと

一番やっておいてよかったことは、技術的なアウトプットなのですが、やり方も工夫すべきかなと思います。例えば、私の場合は、「ペパボに入りたい」と思ったその日に、応募のエントリーフォームを見ました。

f:id:hirotsuru314:20180304235837p:plain

f:id:hirotsuru314:20180304235849p:plain

私はこれを見て、ポートフォリオを作りましたし、GitHubSlideShare(Speaker Deck)の体裁を整えました。まぁこれが受かった理由かはわかりませんが、少なくとも自分自身が自信を持って面接に望めるようになると思うので、その会社がどういったものを見るのか(何を提出するのか)は見ておいて損はないでしょう。

1ヶ月働いて見て

もともとネットですごいなーって見ていた方々と近くで働くのはすごく刺激的です!周りに尊敬するすごいエンジニアがいるというのはエンジニアにとっては何にも変えがたい福利厚生だと思います。
またペパボで1ヶ月働いていいなーと感じたのは、誰かが何かに挑戦しようとしているときに、皆が「やばいじゃん」とか「ええやん」とかすごくポジティブな言葉をかけていて鼓舞していることです。これは見ていて本当に気持ちがいいです。こういう雰囲気だと自分の意見を言いやすくなるし、それで議論が活発になると会社全体としてもいい効果がありそうだなーと思いました。
あと、飲み会に参加したときに印象的だったのが、その場にいない社員のことを「あの人のこういうところはすごい!」とか皆で絶賛していたことです。そういうの最高ですよね。いろんな意味でポジティブな会社だなーって思いました。

これからやりたいこと

1. 得意な(好きな)分野をみつけたい

インフラといっても非常に広い分野なので、その中でも自分が特に好き分野を見つけて、それを得意な分野にしていきたいです。さらに、分野を見つけるだけにとどまらず、OSSの開発をしたり、その成果をイベント登壇やブログを通じてアウトプットしていきたいです。

2. 「比較から学ぶ」をやりたい

エンジニアになってからこれまでの1年ちょいを振り返ると、目の前のことを必死で勉強していただけだったので、知識の幅が狭すぎるなと感じました。そこで最近は、何かの勉強をする上で「比較から学ぶ」というのが大事だなーって思っています。 具体的には以下の2つのアプローチがあると思います。

① 過去との「比較から学ぶ」

これは、「歴史から学ぶ」と言い換えてもいいかもしれませんね。私のように最近プログラミングを始めたばかりの人は過去の技術に対する知識が乏しく、それが現在の技術の理解度にも影響を与えているのではないかと思いました。
その技術は「なぜ生まれたのか」「何の問題を解決したのか」「生まれてからどういう進化をたどったのか」などを知ることは大事だと思ったので、過去の技術を知る努力もしようと思います。

② 他の技術との「比較から学ぶ」

これは同レイヤーの他の技術を学ぶこと、例えば関数型言語を勉強することで、オブジェクト指向への理解が深まるといった類のことを指しています。自分の専門としたい分野に留まらず、視野を広げると、結果として自分の分野にも好影響を及ぼすのでは、と思ってきたので、意識的にやってみたいと思います。

3. ベンチプレス150kg挙げたい

これはそのままですね。せっかく消防士時代に鍛えた体にもう一度鞭を打って、バルクアップしたいと思います。その上で明確な目標がほしいと思ったので、1番好きなトレーニングであるベンチプレスを150kg挙げるという目標を掲げました!

以上、これからはインフラエンジニアとして自分の分野を開拓し、バリバリ活躍できるよう頑張っていくので、これからもよろしくお願いします!!

シリコンバレーのIT企業に行ってきた!

1月の末に約1週間、サンフランシスコ・シリコンバレーに行ってきました!エンジニアになってからすごく興味を持っていたシリコンバレーの有名IT企業を回ってきたので、写真を中心にご紹介します!

Facebook

有名ないいねの看板

f:id:hirotsuru314:20180224210753j:plain  

会社の中は広すぎて、雰囲気も街みたいでした!

f:id:hirotsuru314:20180224211022j:plain

f:id:hirotsuru314:20180224211037j:plain

食堂

f:id:hirotsuru314:20180224211323j:plain

会社の中にゲーセンがあった。 食堂もゲーセンも全部無料でした!すごい福利厚生・・

f:id:hirotsuru314:20180224211350j:plain

Google

オフィスの中にいっぱい人形がいるスポットを発見!

f:id:hirotsuru314:20180224211719j:plain

アンドロイドのマスコットのやつ。でかっっ!

f:id:hirotsuru314:20180224211816j:plain

KitKatバージョン??

f:id:hirotsuru314:20180224212306j:plain

  Googleカラーの自転車。会社の敷地が広すぎるから社員はこれで移動してるっぽい。

f:id:hirotsuru314:20180224211857j:plain

Googleのグッズが買えるショップ。最近できたっぽい。

f:id:hirotsuru314:20180224212117j:plain

f:id:hirotsuru314:20180224212130j:plain

Apple

Apple本社

f:id:hirotsuru314:20180224212726j:plain

一般の人はどこにでもありそうなアップルストアに入れるだけでした。

f:id:hirotsuru314:20180224212739j:plain

f:id:hirotsuru314:20180224212751j:plain

おまけ(サンフランシスコ市内)

サンフランシスコとシリコンバレーは車で1時間弱ぐらいの距離です。サンフランシスコ市内も観光しました。

移動手段はケーブルカー!

f:id:hirotsuru314:20180224214204j:plain

サンフランシスコを代表する観光名所であるフィッシャーマンズワーフのカニの看板

f:id:hirotsuru314:20180224214222j:plain

フィッシャーマンズワーフにはおいしい食べ物がいっぱいあった!
クラムチャウダー

f:id:hirotsuru314:20180224214309j:plain

カニがいっぱい入ったパン

f:id:hirotsuru314:20180224214334j:plain

感想

今回はシリコンバレーの中でも有名な大企業を回りましたが、どこも敷地が超広いし、スケールが大きかったです。建物も日本のオフィス街みたいに高層ビルなどは全くなく、2・3階建の横に広い建物が立っていました。そもそもがアメリカと日本で国土の広さが全然違うので、こういった違いが生まれるのかなーなんて思ってました。
あとは、働く人たちがすごく自由に働いているように感じました。Facebookとか、カフェとかゲーセンとか全部タダで、日中でも常にカフェとかに人がいっぱいいるので「この人たちいつ働いているんだろう?」って感じました。でも、おそらくオンオフの切り替えや時間管理がうまかったりして、そんな人たちでも凄まじく生産性が高かったりするんだろうなー。
シリコンバレーはすごく刺激的(特にエンジニアにとって)な街なので、みなさんもぜひ機会があれば行ってみてください!

Pythonで学ぶUnixプロセスの基礎

 先日、自身が主催するPyFukuokaというイベントにて「Pythonで学ぶUnixプロセスの基礎」というタイトルの発表をしました。スライドはこちらです。

speakerdeck.com

 内容としては「なるほどUnixプロセス」という本を参考にしています。本書は、Unixの基礎から、ファイルディスクリプタの話や、fork(2)を使って子プロセスを生成する方法など、非常に内容が濃く、超オススメです。本の中ではRubyを使って解説されていますが、私の発表では、この本の一部をPythonを使ってまとめました。
 以下、スライドの内容に沿って書いていきます。

動作環境

プロセスにはIDがある

プロセスはプロセスIDという値を持っています。プロセスIDはプロセスの中身とは関連づいていない単に連番になった数値のラベルです。
それではPythonインタプリタを起動して、プロセスに振られたIDを確認してみます。
(補足:Pythonでは「os」という標準ライブラリを利用することで、OS関連の機能を利用することができます。 )

$ python
>>> import os
>>> os.getpid()
1229

「1229」という数値が返ってきました。ターミナルの別ウィンドウでpsコマンドをたたくと、

$ ps -p 1229
  PID TTY           TIME CMD
 1229 ttys000    0:00.06 python

確かにPIDが1229でPythonのプロセス立ち上がっていることがわかります。(PIDはプロセスIDのこと)

プロセスには親がいる

すべてのプロセスには親となるプロセス(親プロセス)がいます。たいていの場合、親プロセスはそのプロセスを起動したプロセスとなります。
Pythonインタプリタを起動して、親プロセスのIDを確認してみます。

$ python
>>> import os
>>> os.getppid()
1165

親プロセスを確認する関数はgetppidで、pが一つ増えていることに注意してください。(parentのp)
Pythonプロセスの親はそれを起動しているbashプロセスになります。

プロセスは子プロセスを作れる

fork(2)システムコールを使うと、実行中のプロセスから新しいプロセスを生成できます。システムコールは、OSの機能を呼び出すための仕組みのことです。
以下、fork(2)システムコールの特徴を挙げます。

  • fork(2)を呼ぶ側のプロセスは「親プロセス」、生成されるプロセスは「子プロセス」となる。
  • 子プロセスは、親プロセスで使われているすべてのメモリのコピーと、親プロセスが開いているファイルディスクリプタを引き継ぐ。
  • 子プロセスがコピーしたメモリは、親プロセス側に影響を与えることなく自由に変更ができる。

それでは、fork(2)を使って、プロセスを生成してみましょう。Pythonからfork(2)を呼ぶためにもosライブラリを利用します。

import os

if os.fork():
    print("Here in the if block.")
else:
    print("Here in the else block.")

上記のプログラムに「fork.py」というファイル名をつけて実行すると、

$ python fork.py
Here in the if block.
Here in the else block.

if句の中もelse句の中もprint文が実行されており、すごく不思議な現象です。
なにが起きてるのか追ってみましょう。

forkの挙動を追ってみる

forkの挙動を追うためにif句・else句それぞれで、以下の2点を調べます。

  1. プロセスIDはどうなっているか
  2. fork関数の返値はどうなっているか
import os

print("---- before fork ----")
print("pid: {0}".format(os.getpid()))
ret = os.fork()

if ret:
    print("----- if block ------")
    print("pid: {0}".format(os.getpid()))
    print("return value: {0}".format(ret))
else:
    print("---- else block -----")
    print("pid: {0}".format(os.getpid()))
    print("return value: {0}".format(ret))

上記のプログラムに「fork_detail.py」というファイル名をつけて実行すると、下のような結果になります。

$ python fork_detail.py
---- before fork ----
pid: 1103
----- if block ------
pid: 1103
return value: 1104
---- else block -----
pid: 1104
return value: 0

まず、プロセスIDについて見てみると、forkする前(プロセスを生成する前)とif句の中が同じプロセスIDで、else句の中はif句に1足したプロセスIDを持っていることがわかります。これは、if句は親プロセス(プロセスID:1103)によって実行されており、else句はfork関数によって生成された子プロセス(プロセスID:1104)によって実行されていることを意味しています。
次に、fork関数の返値を見てみると、親プロセス側には「生成した子プロセスのID」を返し、子プロセス側には「0」を返しています。Pythonの条件式では、正の整数値はTrue、0はFalseと評価されるため、この返値によって、親プロセスはif句の中、子プロセスはfalse句の中と実行内容を分けて記述することができます。

プロセスは通信できる

複数のプロセス間でデータをやりとりするための仕組みのことをプロセス間通信(IPC: Inter Process Communication)と呼びます。 IPCには様々な手法がありますが、今回は比較的単純な「パイプ」を使った方法を紹介します。
パイプは親子関係にあるプロセス間の単方向の通信を実現します。
まず、パイプの概略図を示します。(参考リンク

f:id:hirotsuru314:20171224144448p:plain

forkによって生成された子プロセスは、親プロセスが開いているファイルディスクリプタを引き継ぎます。ファイルディスクリプタとは、OSがカーネルのレイヤーで用意している抽象化の仕組みです。Unixの世界ではファイルシステム上のファイルだけでなく、デバイス、ソケット、パイプなどもファイルとして扱われるため、ファイルディスクリプタが割り振られます。
プロセスがパイプを作ったあとでforkを呼び出した場合、生成された子プロセスは同じパイプを読み書きできるため、親子間でデータを渡すことができます。

import os
reader, writer = os.pipe()
if os.fork():
    os.close(reader)
    write_pipe = os.fdopen(writer, 'w')
    write_pipe.write('Hello child!')
    write_pipe.close()
else:
    os.close(writer)
    read_pipe = os.fdopen(reader, 'r')
    message= read_pipe.readline()
    read_pipe.close()
    print(message)

上記のプログラムに「pipe.py」というファイル名をつけて実行すると、

$ python pipe.py
Hello child!

親から子にメッセージを渡せていることがわかります。

まとめ

プロセスは、IDがあって、親がいて、子を作れて、通信ができます。そして、その様子をPythonから確認できます。今回紹介した内容は、特にPythonである必要はありません。しかし、Pythonなどの高級言語を使って、OS周りの理解を深めることは、学び方の一つのアプローチとして有効だと私は考えます。
なるほどUnixプロセス」という本には今回の内容以外にも様々なプロセスの特徴について解説しています。 下記がその例です。

  • 孤児プロセス
  • ゾンビプロセス
  • デーモンプロセス
  • ソケット通信

この辺りに興味がある方はぜひ本を読まれることをオススメします!

消防士からエンジニアに転職して1年が経った

 消防士として働いていた私がゼロからプログラミングを始めて、ITエンジニアに転職してから1年が経ちました。今回は、1年間エンジニアとして働いた今の率直な思いと、1年の振り返りをしていこうと思います。

転職してどうだったか

 私はエンジニアという仕事は自分に合っているし、転職して本当によかったと思っています。しかしそれは、すべての人に「ITエンジニアっていいよ!」って勧められるというわけではなく、当たり前のことですが、合う・合わないがあると思います。
 私が思うエンジニアの仕事が合う人の特徴は「学ぶことが好きだ」ということに尽きると思っています。正直、エンジニアとして1年間働いて、未経験でもそこそこいけるなっという感覚の方が強かったです。それは、目の前の業務にだけ集中し、業務で必要な技術だけを追えばなんとか仕事はこなせるようになるからです。しかし、そのような場合は大抵、業務で使うフレームワークやライブラリの使い方の暗記ゲームをしているだけで、自分自身にはそんなに力がついてない状態だと思います。(私も最初の半年は完全にこの状態でした。。)
 私は目指すべきゴールを、所属している組織で活躍することではなく、エンジニアとしての市場価値を高めるところに置くようにしました。技術が高く、市場価値が高い状態がエンジニアとしての安定なんだと思います。

エンジニアとして大切にしていること

 私がエンジニアとして働く上でテーマに掲げているのが「継続的なインプットとアウトプット」です。出社前や退社後に毎日勉強し、定期的にイベントへの登壇やブログへのアウトプットを心がけています。インプットとアウトプットはどちらも大切ですが、両者のバランスに悩むことはしばしばあります。その両者のバランスについて、ある方から「アウトプットするのはインプットが溢れ出たとき」という話を聞きました。なるほど、確かにインプットして新しいことを覚えると、人に話したくなりますもんね。私はこれからはとにかくインプットしまくって、それが溢れ出るように、開発・登壇・ブログなどへアウトプットできるようにしたいと思います。意識の上で重きを置くのはアウトプットではなくインプットの量です。

1年間でやったことのまとめ

ライブラリ開発

Banpeiという異常検知ライブラリを作りました。特異スペクトル変換という手法を用いて時系列データの変化点検知が行えるのが特徴です。

github.com

f:id:hirotsuru314:20171115011620p:plain

<デモ動画>


特異スペクトル変換による傾きの変化点検知

<関連記事>
異常検知ライブラリを作ってみた - Fire Engine
特異スペクトル変換法による時系列データの異常検知(Python) - Fire Engine

イベント登壇

Date Event Slide
2017/11/18 九州 Data Scientist MeetUp 2017 異常検知ライブラリを作った話
2017/10/26 PyFukuoka #2 Pythonによるパッケージ開発〜異常検知パッケージをつくってみた〜  
2017/09/20 プログラマのための数学勉強会@福岡 #6 異常検知の基礎と実践 〜正規分布による異常検知〜
2017/09/01 PyFukuoka #1 Pythonによる可視化まわりの話
2017/06/17 第3回データサイエンスLT&勉強会 in LINE福岡! 時系列データ分析とPython〜カルマンフィルタによる状態推定〜
2017/01/30 Oisix機械学習勉強会 トピックモデルでテキストをクラスタリングしてみた
2017/01/15 第2回データサイエンスLT&勉強会 ニューラルネットワークでニュース記事を自動分類してみた

コミュニティ活動

PyFukuokaというコミュニティを立ち上げ、福岡でPythonを盛り上げるために活動中です。定期的にLTイベントを開催し、私も登壇してます!

f:id:hirotsuru314:20171126223507p:plain

正直どれをとっても納得する部分はないですが、こうやって過去の活動をまとめるのは自分の現在の状況を顧みるいい機会になりそうです。

これからやりたいこと

 私はこれまでの1年間は主に機械学習統計学といったデータサイエンスの分野に取り組んできました。その中で感じたことは、データサイエンスは課題解決の強力なツールの一つだということです。私はデータサイエンスに主軸を置くのではなく、興味のある分野の仕事をしながら、そこで出てきた課題をデータサイエンスの力で解決することがおもしろいと思っています。
 現在一番興味がある分野が、これまで全くといっていいほど触れてこなかったインフラです。アプリケーション側よりもそれらを支えるインフラ側に興味がシフトしてきました。また、ITインフラの現場においても、サーバのCPUやメモリ等の各種リソースの使用量、ネットワークトラフィック、I/O要求といった時系列データが時々刻々と蓄積されており、データサイエンスの応用の幅は広いと思います。中でも、システム監視の自動化に興味があります。(Banpeiを使って挑戦してみたい)データサイエンス抜きに考えても、ネットワークやOSまわりにはかなり興味が惹かれています。将来的にはインフラとデータサイエンスの二つの知識・技術をもったエンジニアになりたいと考え始めました。

さいごに

 本当の天才達からすると、多くの人間は地頭の差なんて誤差の範囲であり、一番大事なのはマインドの部分だと思います。さいごに、未熟者の私のマインドを常に押し上げてくれた方々の記事を紹介させていただきます。どの記事も本当に素晴らしく俄然モチベーションがあがります。

異常検知ライブラリを作ってみた

今回の記事は、前職消防士でゼロからプログラミングを始めた超未熟者の私が、異常検知ライブラリを作った話です。

なぜ作ったか

マインド的背景

消防士を辞めてエンジニアに転職してから1年、いろんな技術に触れました。TensorFlow、scikit-learn、Dockerなどなど、必死になって使い方を覚えました。しかしだんだん、「これ、コマンド覚えてるだけで自分に何も技術身についてなくない?」という疑問や焦りが湧いてきて、自分は便利なツールを使いこなせるようになりたいんじゃなくて、いつの日かみんなが使って便利なツールを作る側になりたいんだ、ということに気づきました。そのような思いから今回初めてライブラリを作り、公開してみました。

データサイエンス的背景

 世の中は時系列データで溢れています。ビジネスの場において、データの何かしらの変化の兆候を捉えて、いち早く意思決定をしたいという場面がよくあります。例えば、観測データが商品の受注データであれば、急に売れなくなった(もしくは売れ出した)変化をいち早く検知し、次の一手を打てると機会損失を防げるかもしれません。私も実際に業務で時系列データの変化を捉えたい場面がありましたが、変化の兆候というのはデータによって様々であり、明らかなものもあれば、そうでないものもありました。様々なタイプの変化に対して柔軟に検知するためには、閾値やルール設定といった力技では難しく、コンピュータに「いつもと違う状態」を自動に検知させることが求められています。これがいわゆる異常検知(ここでは特に変化点検知)の技術です。

作ったもの

ソースコード

Banpei(番兵)
github.com

使い方

インストールは下記のコマンドで行います。(Pythonパッケージ化はしていますが、PyPIには登録していないのでgit cloneしたのちにpip installする必要があります。)

git clone https://github.com/tsurubee/banpei.git
cd banpei
pip install .

一番簡単な使い方は、異常を検知したいデータをPythonのリスト型やNumPy arrayなどの配列にして、detect関数に渡してやると、データの変化度を計算して配列を返します。
下記のコードのように3行のみで異常検知できます。まず、Banpeiをimportし、SSTという特異スペクトル変換法(異常検知アルゴリズムの一つ)を使うためのインスタンスを作成し、detect関数にインプットデータを渡すだけです。インスタンス作成時の引数wはスライド窓の幅であり、チューニングが必要なパラメータです。(詳しくは以前の記事を参照ください)

import banpei
model   = banpei.SST(w=50)
results = model.detect(data)

resultsには変化度が入っており、インプットであるdataと、アウトプットであるresultsを可視化すると、
(data: 緑、results: 赤)※見やすいように縦軸は調整しています。

f:id:hirotsuru314:20171115011620p:plain

このようにデータの兆候が変化する点で、変化度である赤線が立ち上がっているのが見て取れます。この赤の立ち上がりをみることで変化点検知ができそうです。

リアルタイム異常監視への挑戦

もともと私がBanpeiをつくったときは、リアルタイム監視の環境を作りたいというモチベーションがありました。(売り上げのリアルタイム監視やサーバリソースのリアルタイム監視などなど)そこで今回、ブラウザを通してリアルタイム異常監視ができる簡単な仕組みを構築しました。
可視化のツールとしてBokehというPython製の可視化ライブラリを採用しました。簡単なデモ動画を載せておきます。動画の10秒あたりで変化点検知をしています。


特異スペクトル変換による傾きの変化点検知

このようなリアルタイム監視環境を構築したければ、pip install bokehでBokehをインストールして、下のようなスクリプトで可視化できます。(私の環境ではbokeh==0.12.10)

import banpei
import numpy as np
import pandas as pd
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, DatetimeTickFormatter
from bokeh.plotting import figure
from datetime import datetime
from math import radians
from pytz import timezone

data = []
results = []

def get_new_data():
    global data
    #ここでデータ取得(例:new_data = psutil.cpu_percent())
    data.append(new_data)

def update_data():
    global results
    get_new_data()
    ret = model.stream_detect(data)
    results.append(ret)
    now = datetime.now(tz=timezone("Asia/Tokyo"))
    new_data = dict(x=[now], y=[data[-1]], ret=[results[-1]])
    source.stream(new_data, rollover=500)

# Create Data Source
source = ColumnDataSource(dict(x=[], y=[], ret=[]))
# Create Banpei instance
model = banpei.SST(w=30)
# Draw a graph
fig = figure(x_axis_type="datetime",
             x_axis_label="Datetime",
             plot_width=950,
             plot_height=650)
fig.title.text = "Realtime monitoring with Banpei"
fig.line(source=source, x='x', y='y', line_width=2, alpha=.85, color='blue', legend='observed data')
fig.line(source=source, x='x', y='ret', line_width=2, alpha=.85, color='red', legend='change-point score')
fig.circle(source=source, x='x', y='y', line_width=2, line_color='blue', color='blue')
fig.legend.location = "top_left"
# Configuration of the axis
format = "%Y-%m-%d-%H-%M-%S"
fig.xaxis.formatter = DatetimeTickFormatter(
    seconds=[format],
    minsec =[format],
    minutes=[format],
    hourmin=[format],
    hours  =[format],
    days   =[format],
    months =[format],
    years  =[format]
)
fig.xaxis.major_label_orientation=radians(90)
# Configuration of the callback
curdoc().add_root(fig)
curdoc().add_periodic_callback(update_data, 1000) #ms

get_new_data関数の部分をカスタマイズして、独自のデータ取得の処理を書けば、bokeh serve file名.pylocalhostが立ち上がりブラウザ上で上記の動画のようなものが見れます。Banpei側ではstream_detectという関数を使っていますが、これは渡されたインプットデータのうち、最新の1点の変化度を計算して、数値を返す関数です。

Banpeiの異常検知アルゴリズム

 Banpeiには異常検知アルゴリズムとして特異スペクトル変換を採用しています。概要は以前記事に書きました。

blog.tsurubee.tech

特異スペクトル変換の特徴としては、特定の確率分布を仮定していないため、①多様な変化に頑強に対応できる、②パラメータチューニングが容易、などが挙げられます。パラメータに関しては、スライド窓のサイズwの実質1個のみのチューニングで済みます。(他の必要なパラメータはwから経験的に算出)特異スペクトル変換は愚直に線形計算しているだけ、というところがすごく素敵です。
一方、内部の計算過程で特異分解と呼ばれる行列計算を用いており、これがかなり計算コストが高いです。したがって、検知を行いたい時間間隔や、パラメータwの最適値によっては、リアルタイム計算が厳しくなるかもしれません。
今後、特異スペクトル変換の高速化のために、クリロフ部分空間を用いた下の論文を実装したいと思ってます。

http://epubs.siam.org/doi/pdf/10.1137/1.9781611972771.54

さいごに

 作った感想としては、やっぱりモノ作り楽しいな、です。一旦、画面上で動くものまで作れたのですごく達成感はありました。ここからどのように実際の現場で使えるように持っていくかは真剣に考えています。例えば、前述のアルゴリズムの高速化でよりリアルタイム性を高めたり、メールやSlackに異常通知を送れる機能を実装したりと・・まだまだやりたいことが山積みなので、着実に開発を進めていきたいと思っています。

参考資料

異常検知って?

時系列データって?

ライブラリ(パッケージ)開発するには?

Bokehって?

特異スペクトル変換って?

Bokehを用いたビットコイン価格のリアルタイム可視化(Python)

 今回はBokehというライブラリを使って、ビットコイン価格のリアルタイム可視化を行う方法について書いていきます。Bokehを使うと、Pythonオンリーで可視化までできるので、非常に便利です。

Bokehとは

 BokehとはPython製の対話的可視化ライブラリです。対話的とはどういうことかというと、作成したグラフのズームアップ・ズームアウト・軸の調整といった様々な操作を手元で簡単に行えるということです。

f:id:hirotsuru314:20171111151354p:plain

これは、Bokeh内部で、HTML・CSSJavaScriptを自動で生成する仕組みがあることで達成されており、ユーザは基本的にはPythonコードしか書く必要がありません。
Bokehの公式のギャラリーを見るとすごくファンシーな可視化がたくさんあるので見るだけでも楽しいです!(実際に対話的な操作もできます)

Gallery — Bokeh 0.13.0 documentation

以前の記事に対話的なグラフの作成方法を書きましたので、併せてどうぞ

Bokehの特徴

 Bokehの特徴をいくつか挙げると、

  • 対話的な可視化ができる
  • ストリーミングデータのリアルタイム可視化ができる
  • グラフを作るためにHTML・CSSJavaScriptを書かなくてもよい

といった感じです。
今回は『ストリーミングデータのリアルタイム可視化ができる』に着目した記事になります。
対話的というだけでもすごいのですが、動的に流れてくるデータに対しても、Bokehにデータを流すことでリアルタイム可視化ができ、これが本当に便利です!

用いるデータについて

 ストリーミングデータの例として『ビットコイン価格』を取り上げて見ることにします。(私自身はシステムトレードなどは全くやっていません。笑)
 下記リンクの中のLast Tradeという部分を取得します。この部分は、数秒ごとにリロードすると値が変わるので、定期的にスクレイピングすることで、時系列データを得ることができます。

Bitcoincharts | bitFlyer - JPY Summary

f:id:hirotsuru314:20171111151600p:plain

ビットコイン価格のリアルタイム可視化

開発環境
Python 3.6.3
bokeh==0.12.10
beautifulsoup4==4.6.0
requests==2.13.0

作成プログラム

from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, DatetimeTickFormatter
from bokeh.plotting import figure
import requests
from bs4 import BeautifulSoup
from datetime import datetime
from math import radians
from pytz import timezone

URL = "https://bitcoincharts.com/markets/"
MARKET="bitflyerJPY"

def get_data():
    res = requests.get(URL + MARKET + ".html", headers={"User-Agent": "Mozilla/5.0"})
    content = res.content
    soup = BeautifulSoup(content, "html.parser")
    last_trade = int(soup.find_all("p")[0].span.text)
    return last_trade

def update_data():
    now = datetime.now(tz=timezone("Asia/Tokyo"))
    new_data = dict(x=[now], y=[get_data()])
    source.stream(new_data, rollover=200)

#データソースの作成
source = ColumnDataSource(dict(x=[], y=[]))

#グラフの作成
fig = figure(x_axis_type="datetime",
             x_axis_label="Datetime",
             y_axis_label="Last Trade",
             plot_width=800,
             plot_height=600)
fig.title.text = "Bitcoin Charts"
fig.line(source=source, x="x", y="y", line_width=2, alpha=.85, color="blue")
fig.circle(source=source, x="x", y="y", line_width=2, color="blue")

#軸の設定
format = "%Y-%m-%d-%H-%M-%S"
fig.xaxis.formatter = DatetimeTickFormatter(
    seconds=[format],
    minsec =[format],
    minutes=[format],
    hourmin=[format],
    hours  =[format],
    days   =[format],
    months =[format],
    years  =[format]
)
fig.xaxis.major_label_orientation=radians(90)

#コールバックの設定
curdoc().add_root(fig)
curdoc().add_periodic_callback(update_data, 3000) #ms単位

上記プログラムにbitcoin_streaming.pyというファイル名をつけ、コマンドラインから下のように実行すると、私の場合

$ bokeh serve bitcoin_streaming.py
2017-11-11 12:24:32,513 Starting Bokeh server version 0.12.2
2017-11-11 12:24:32,520 Starting Bokeh server on port 5006 with applications at paths ['/bitcoin_streaming']
2017-11-11 12:24:32,520 Starting Bokeh server with process id: 1995

のように表示され、http://localhost:5006/bitcoin_streaming にアクセスすると、下のようにグラフが表示されます。

f:id:hirotsuru314:20171111151837p:plain

実際はリアルタイムで動くグラフなので、動画にしてみました。


Bokehを用いたビットコイン価格のリアルタイム可視化

ちょっとわかりづらいですが、3秒に1回スクレイピングビットコイン価格を取得して、リアルタイムに可視化しています。

プログラムについての補足

Beautiful Soupドキュメント — BeautifulSoup Document 3.0 ドキュメント

  • ColumnarDataSource
    BokehにはColumnarDataSourceというデータ構造があります。ColumnarDataSourceは 列指向でデータを保持します。
    リアルタイム可視化の場合には、このデータ構造にstream関数を使って、データを流し込むとBokehの方でデータを保持から可視化までやってくれます。
    ちなみに
def update_data():
    now = datetime.now(tz=timezone("Asia/Tokyo"))
    new_data = dict(x=[now], y=[get_data()])
    source.stream(new_data, rollover=200)

ここのstream関数の引数であるrolloverは、保持するデータ数の上限を決めるもので、その上限がそのまま可視化にも反映されるので、重要です。

  • コールバック
    作成したプログラムの最後の行の
curdoc().add_periodic_callback(update_data, 3000)

部分で、update_data関数を3秒(3000ms)に1回呼び出す設定を書いていて、update_data関数が実行されると、ColumnarDataSourceのインスタンスに新しいデータが追加され、可視化される仕組みになっています。

参考

おわりに

 上記のソースコードはデータ取得のget_data関数の部分だけ変えるといろんなリアルタイム可視化ができます。例えば、CPU使用率を監視しようと思うと、こんな感じに入れ替えればできます

import psutil
def get_new_data():
    return psutil.cpu_percent()

 いろいろ楽しいことができそうですね!

ちなみに・・・

 私は最近個人で『Banpei』という異常検知ライブラリを作っており、Bokehと連携させ、リアルタイム異常監視などができるように開発を進めています。リアルタイム異常監視については近日中にまた記事にします!
 異常検知ライブラリBanpeiはGitHubで公開しているので見ていただけると嬉しいです!

github.com

Pythonによる「べき乗法(Power Method)」の実装 - 最大特異値・特異ベクトルを求める

はじめに

 理工学の分野において、行列の固有値固有ベクトルを求める問題に直面することが多々あります。一般に、固有値固有ベクトル固有値分解という行列分解で求められますが、これは正方行列に対してのみ定義されています。特異値分解は、固有値分解を長方形行列に拡張したものであり、より適用範囲が広く、画像圧縮や自然言語処理における潜在意味解析などにも応用されています。特異値分解は比較的計算コストが高いため、例えば絶対値最大の特異値やその特異ベクトルを求めたいだけの場合は、他の手法を使った方が計算効率が高いです。
 今回はべき乗法(Power method)と呼ばれる数値計算アルゴリズムPythonで実装し、行列の最大特異値および特異ベクトルを求める方法について書いていきます。

Pythonによる実装

 べき乗法の理論については、下記リンクの資料(P5〜)を参考にしました。

http://www.cs.yale.edu/homes/el327/datamining2013aFiles/07_singular_value_decomposition.pdf

import numpy as np

def power_method(A, iter_num=1):
    """
    Calculate the first singular vector/value of a target matrix based on the power method.
    Parameters
    ----------
    A : numpy array
        Target matrix
    iter_num : int
               Number of iterations

    Returns
    -------
    u : numpy array
        first left singular vector of A
    s : float
        first singular value of A
    v : numpy array
        first right singular vector of A
    """
    # set initial vector q
    q = np.random.normal(size=A.shape[1])
    q = q / np.linalg.norm(q)

    for i in range(iter_num):
        q = np.dot(np.dot(A.T, A), q)

    v = q / np.linalg.norm(q)
    Av = np.dot(A, v)
    s = np.linalg.norm(Av)
    u = Av / s

    return u, s, v


特異値分解との比較

 特異値分解の結果と一致しているか確かめます。Python特異値分解を行うにはNumPyのlinalg.svgを使うとよいでしょう。
以下のように4×3行列を作成します。

A = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])

power_method関数を使うと、

u, s, v = power_method(A, iter_num=1)

u
#-> array([-0.14091245, -0.34396479, -0.54701712, -0.75006945])
s
#-> 25.462398134435947
v
#-> array([-0.50388184, -0.57446659, -0.64505134])

NumPyで特異値分解を行うと

U, S, V = np.linalg.svd(A, full_matrices=False)

U
#-> array([[-0.14087668,  0.82471435,  0.54771037],
#         [-0.34394629,  0.42626394, -0.72755711],
#         [-0.54701591,  0.02781353, -0.1880169 ],
#         [-0.75008553, -0.37063688,  0.36786364]])
S
#-> array([  2.54624074e+01,   1.29066168e+00,   1.97614008e-15])
V
#-> array([[-0.50453315, -0.5745157 , -0.64449826],
#         [-0.76077568, -0.05714052,  0.64649464],
#         [-0.40824829,  0.81649658, -0.40824829]])

両者を比較すると、左特異ベクトル、特異値、右特異ベクトルがほぼ一致していることがわかります。