DockerCompose で ES + Kibana 環境を構築しました!
前回の記事で ElasticSearch と Kibana の両方を Docker で起動する方法を紹介した。
しかし、 Docker の Link などを使って管理していたので、手動で管理するものがたくさんあり、大変だった。
なので、今回は Docker Compose を利用する方法を紹介する。
Docker Compose
今回の ElasticSearch と Kibana のように、複数のコンテナを連携して管理する方法をオーケストレーションという。
このオーケストレーションの方法はいくつかあり、今話題の Kubernetes などがあるが、今回はその一つの Docker Compose を使った。
Kubernetes の場合、複数の pods 間で service discovery を行ってくれる機能や pods のスケーリングなどの機能がある。
Docker Compose は kubernetes のような複雑な機能はないが、一つのファイルで複数のコンテナを管理できる。
なので、今回のように実験的に環境を作ることに関して言えば、 Kubernetes は必要過多なサービスになると判断して、
Docker Composeでコンテナのビルド、起動、シャットダウンなどを一括で管理できるメリットを取った。
ES + Kibana with Docker Compose
複数のファイルにまたがるので、 Github 上にリポジトリを作成しました。
ElasticSearch も Kibana も 6.4.0
を利用している。
ElasticSearch
日本語の文章を解析するためには形態素解析器を使って文章を品詞ごとに分解する必要がある。
そのため、 ElaskticSearch の Dockerfile では、形態素解析器に elasticsearch-analysis-kuromoji のパッケージをダウンロードした。
パッケージをダウンロードするために標準入力でインストールを許可する入力が必要となる場合がある。
Docker でイメージをビルドする際、標準入力できないので、今回は echo y
を利用して y
を標準入力している。
docker-compose.yml
このファイルの中で ElasticSearch と Kibana のイメージを管理している。
yaml
ファイル形式になっており、読み方は key-value として読んでいくと良いかと思う。
詳しくは、リファレンス
Compose ファイル・リファレンス — Docker-docs-ja 17.06.Beta ドキュメント
を読むのがいいと思う。
このリポジトリ内で注意するべき点がいくつかあるので、以下にメモしておく。
build
4行目と24行目に build
key がある。
これは value 名のディレクトリに存在する Dockerfile をビルドする。
なので、ここの value は docker-compose.yml
がある構築用コンテクストのパスを指定する必要がある。
そして、各行の次の行で container-name
でコンテナ名をつけている。こちらはときにディレクトリとは関係ないので自分が好きな名前をつければ良い。
ちなみに build 済みのイメージを利用することもでき、 image
を利用し、イメージ名を指定すればよい。
詳しくは、
Compose ファイル・リファレンス — Docker-docs-ja 17.06.Beta ドキュメント
に記載されている。
volumes
Docker は基本的にコンテナ内のデータは揮発性であり、毎回コンテナを起動すると初期状態で起動される。
つまり、前回 ElasticSearch に入力したデータは残らない。
データを保存したい場合、 docker の data volume として保存する必要がある。
今回は、 docker-compose.yml
の30行目で data volume をローカルの docker 環境に構築し、利用するようにしている。
なので、真新しい環境を構築するためには、コンテナの再構築・再起動以外に、この volume を削除する必要がある。
辞書のマウント
kuromoji で形態素解析を行うにあたり、カスタマイズした辞書を利用するように template
に記載している。
そのため、 docker-compose.yml
の8行目で辞書をマウントしている。
この辞書は csv
形式で1行が一つの形態素解析の結果を表しており、
本文中の形式,形態素解析の結果,ヨミ,品詞
の形にしなければならない。
まとめ
今回は DockerCompose を利用した ES + Kibana 環境を構築した。
ES 環境で日本語の解析を試しに行いたい場合は、お手軽に試せるので是非活用してください。
わからないところなどはコメントや Github の issue にお願いします!
Docker で ElasticSearch + Kibana 環境を構築してみた。
本業で ElasticSearch を使うシステム開発を行うことになった。 機能の POC を確認するため、 Docker で ElasticSearch と Kibana を起動することにした。 自分は Docker for Mac を利用している。
Note
Linux 環境や Mac ユーザだけど Docker for Mac を利用されていない方は、各コンテナに接続確認する場合の localhost
部分を適宜 $(docker-machine ip <machine-name>)
に置換してください。
ネットワーク
複数のコンテナを利用するので、 Bridge ネットワークでコンテナ同士をつなぐ。
docker network create elasticsearch --driver bridge
コンテナ
ElasticSearch コンテナ起動
docker run -d \ -e "http.host=0.0.0.0" \ -e "transport.host=127.0.0.1" \ -e "xpack.security.enabled=false" \ -e "xpack.monitoring.enabled=false" \ -e "xpack.watcher.enabled=false" \ -e "xpack.graph.enabled=false" \ -e "xpack.ml.enabled=false" \ -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ -p 9200:9200 \ -p 9300:9300 \ --name elasticsearch \ --network="elasticsearch" \ docker.elastic.co/elasticsearch/elasticsearch:6.4.0
ElasticSearch コンテナが起動しているかを確認するには、
curl localhost:9200
でレスポンスを見てみる。 以下、みたいなレスポンスが返ってきた。
{ "name" : "MAzzjYI", "cluster_name" : "docker-cluster", "cluster_uuid" : "wEvHKuikQyeE1Ca7UogUZQ", "version" : { "number" : "6.4.0", "build_flavor" : "default", "build_type" : "tar", "build_hash" : "595516e", "build_date" : "2018-08-17T23:18:47.308994Z", "build_snapshot" : false, "lucene_version" : "7.4.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" }
Kibana コンテナ起動
docker run -d \ --name kibana \ -p 5601:5601 \ -e "ELASTICSEARCH_URL=http://elasticsearch:9200" \ -e "xpack.graph.enabled=false" \ -e "xpack.security.enabled=false" \ -e "xpack.ml.enabled=false" \ --network="elasticsearch" \ docker.elastic.co/kibana/kibana:6.4.0
Kibana が起動しているかはブラウザでチェックする。
open http://localhost:5601
できた。
所管
恐らく本当は k8s とか何かしらのオーケストレーションツールを使って管理するのが望ましい気がする。 本当は kubernetes を使ってやりたいけど、一旦、次は docker-compose を使ってみようかな。
Prestoで2つの配列を同時に行に変換する
最近、Treasure Dataに触る機会が増えた。 その中で1行に2つのカラムにそれぞれコンマ区切りのデータがあり、それを行展開するのにハマったポイントがあったのでメモしておく。 そして長くなるので、最初に結論を載せておく。
どういうデータか
以下のようなデータ形式を想定している。
column1 | column2 |
---|---|
a,b,c | 1,2,3 |
そして、このデータを以下のデータ形式に展開する。
column1 | column2 |
---|---|
a | 1 |
b | 2 |
c | 3 |
前提としては、column1もcolumn2も同数の要素数が含まれていることとする。
どうするか
以下のように CROSS JOIN UNNEST
の中に複数の split
関数を入れる。
SELECT clmA, clmB FROM tbl1 CROSS JOIN UNNEST( split( tbl1.column1, ',' ), split( tbl1.column2, ',' ) ) AS t(clmA, clmB);
これにより上記のような展開ができます。
どういうことをしたか
ググってみると、Prestoでカンマ区切りのデータを展開した記事はよく出てくる。
これを見たときは、『そっか、じゃあ CROSS JOIN UNNEST をもう1段挟めばいいのか』
と安易に考えてしまいました。
ざっと以下のような感じ。()
SELECT t1.clmA, t2.clmB FROM tbl1 CROSS JOIN UNNEST( split( tbl1.column1, ',' ) AS t1(clmA) CROSS JOIN UNNEST( split( tbl1.column2, ',' ) ) AS t2(clmB);
そうすると以下のような結果が得られた。
column1 | column2 |
---|---|
a | 1 |
a | 2 |
a | 3 |
b | 1 |
b | 2 |
b | 3 |
c | 1 |
c | 2 |
c | 3 |
まさに CROSS JOIN。
なので、次は、『そっか、じゃあWITH句をつかって、一回テーブルとして展開してから、ID付きで展開すればうまくいくんじゃね?』 と思い、やりました。 が、やはり上記のようなクエリの結果になってしまいました。 なかなか奥深いですね…
勉強会に登壇してきました。
機械学習エンジニアとして副業をしていることもあり、FindyでCTOしている @ma3tk からのお誘いもあり登壇してきました。
engineer-parallel-work.connpass.com
発表資料はこちら。
副業をしていて感じていることを話してきた。
まだ初めて数ヶ月だが、いい振り返りになった。
視座を高めるために知識の input だけでなく、 output を行い経験値を増やすこと。
そのサイクルを回すことで自身の視座を高くしていく努力をしていくこと。
それを再認識できた発表だった。
懇親会であった質問
ものすごくありがたいことに参加者から、『発表が良かった』と言ってくださる方が多くいらっしゃった。
その方々といろいろ話すことができたので、質問と自分の考えをまとめておく。
始め方はどうされたのですか?
僕の場合、スライドに載せているFISMという会社のCTOをしている @kzkohashi と同僚であり、
そのつながりで始めることができた。
元々興味はあったが、なかなか始める決意がつかなかったが、とてもいいきっかけをくれたと思っている。
いいなと思っている会社があり、そこで副業できればなと思っていますが、どう思いますか?
副業として受け入れてくださるのであれば、積極的に取り組めばいいと思っている。
正直、外から見ていていいなーと思う会社はいっぱいある。
ただ、入ってみて、あれ?と思うこともある。
そうなってしまったときに、現職を退職してまでって後悔がすごく、これが現職にとどまり続ける要因の一つなのかと思う。
そういう意味では副業として参加し、正社員と肩を並べて働いてみて、直に会社の文化に触れることはいいスタートの切り方なのではないだろうか?
そして、思ったより肌に合わないと判断できた場合は、信頼を稼いでつながりを構築することに専念すればいいと思っている。
アウトプットの仕方どうしていますか?
データ分析の場合、データを分析して最後の可視化と考察の部分が重要だと思っている。
結局のところ期待されているのはアウトプットであり、例えば、
- 必要なタスクに対して実現可能なのか不可能なのか
- 不可能な場合、だいたい手段はあるのかもっと作業を進めれば可能性はでてくるのか
などが期待されているのではないだろうか。
データ分析といえばデータの前処理が重要であることは重々承知している。
しかし副業だと、発注者側にデータ分析を理解してくれる人がいない場合、
最初から前処理をコツコツやっていても、「何してるんですか?」ってなると思う。
それよりも、
「既存のデータを使ってこうしました。ここの部分の改善をすることで精度があがると思うので、次にここを着手します。」
と道筋を見せて上げたほうが納得感が得られるし、自分自身必要な処理が見えてくるはずである。
なので、アウトプットを細かく見せることはすごく重要だと考えている。
ポートフォリオはどうされていますか?
ここで悩んでいる人が多かった印象だった。
正直、自分は知り合いの相談から副業がスタートしているので、ポートフォリオとかを考えなくても良かった。
しかし、もっといろんな会社の案件を触ってみたいなと思ったときに、ポートフォリオをつくらないとなとなった。
恐らく、ポートフォリオとして上がるのは、「エンジニアならgithubだろ!」というのが定石なのではないだろうか。
その声がすごく多かったし、それには賛成である。
しかし、機械学習のエンジニアはデータがないとなかなかアピールポイントがないので、
githubにコードを上げることは難しいのではないかと思う。
そこで僕はブログもセットでポートフォリオを組むといいのではないかと思っている。
データ前処理の中で得られた気づきは、大抵、gistやgithubに上げるほどではない。
だが、その一部をブログとして残すことで自分がどの程度データに触れているのかというのをアピールすることに繋げられる。
積もり積もればれっきとした履歴書であるし、コード以外の考えをアピールすることができるのでよいと思う。
ブログのアウトプットについては @kakakakku のブログに対する考え方は参考になるので、
一度は読まれたほうがいいと思う。
最後に
今回は本当に貴重な会で登壇させていただけた。 今後もいろんな勉強会に登壇できるよう、自分の成長に繋げられるように input/output を引き続きがんばろう。
PandasでJSONファイルを保存するときの注意点
個人的にはTensorFlowでモデリングするようになってから、numpyを触る頻度が高くなって、それに伴いpandasを触る機会が減っていった。
データの可視化もpyplotとかseaboneとかを使って、可視化の方法を調べながら模索している状況が増えていった。
しかし、先日副業の案件で学習データをサクッと可視化させようと思ったときに選んだのは、pandasだった。
やはりpandasは優秀かな
テキストデータを読み込んで、異常値を探すとかその場でフィルタリングしようかと思うと、
やはりpandasは楽だなと改めて思たった。(ただnumpy力が低いだけかもしれないが…)
例えば、とりあえずテキストに保存したデータセットをDataFrameに読み込んで、
データセットの概要を知りたいなーと思ったら、
df.describe()
とすれば、カラム毎の最大値・最小値、四分位数とかを出してくれるし、
histグラムで可視化したいなーとおもったら、
df.plot.hist(y=[<可視化するカラム名>], bins=<ビン数>)
とすれば、指定したカラムだけのヒストグラムが可視化できて、すごく楽だった。
分布はどのような形なのか、どの範囲で異常値を切り出すのかなど判断して
そのままDataFrameでフィルターすればいいので、短時間でデータセットのチェックと更新ができた。
DataFrameの保存には注意が必要
前提
前提として、データセットはJSON形式で保存しており、
すでに実験するノートブックを用意してある、としてます。
なので、同じJSONの形式で保存することで実験のノートブックを再利用することが前提です。
問題点
通常、PythonでdictionaryをJSONとして保存した場合、
import json dict_list = [] for i in range(10): dict_obj = {} dict_obj["single"] = i dict_obj["double"] = i * 2 dict_obj["square"] = i * i dict_list.append(dict_obj) with open('dict2json.json', 'w') as f: json.dump(dict_list, f)
のような処理になる。 そして、この場合テキストにはざっくりと
[{"single": 0, "double": 0, "square": 0}, {"single": 1, "double": 2, "square": 1}, {"single": 2, "double": 4, "square": 4}, {"single": 3, "double": 6, "square": 9}, {"single": 4, "double": 8, "square": 16}, {"single": 5, "double": 10, "square": 25}, {"single": 6, "double": 12, "square": 36}, {"single": 7, "double": 14, "square": 49}, {"single": 8, "double": 16, "square": 64}, {"single": 9, "double": 18, "square": 81}]
このような形式で保存されている。
いわゆる、key-valueが配列に格納されている形である。
なので、配列の1要素が1行(1レコード)のデータを表現している。
そして、何も意識せずにDataFrameをJSONファイルとして保存すると痛い目に遭った。 それが今回の教訓だった。
PandasでJSONを読み込むには、
import pandas as pd df = pd.read_json('dict2json.json')
とすればよい。 そして、このDataFrameをJSONとして保存する場合、以下のように書けば良い。
df.to_json("df2json.json")
人によりけりだとは思うが、簡単で直感的だ。ただし、この場合保存されたファイルの中身は、
{"double":{"0":0,"1":2,"2":4,"3":6,"4":8,"5":10,"6":12,"7":14,"8":16,"9":18},"single":{"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9},"square":{"0":0,"1":1,"2":4,"3":9,"4":16,"5":25,"6":36,"7":49,"8":64,"9":81}}
となる。
形としては、カラム毎に、 "index: value"
の形で保存されている。
この場合、保存されたファイルを
with open('df2json.json') as f: json_data = json.load(f)
として読み込むと、1レコードずつとして読み込まれず、keyが想定していたものと異なる結果になる。
Key-Valueの形式で保存するには、
DataFrameをkey-valueの形式のJSONに保存するときには orient
オプションに record
を指定する必要があることに注意する。
df.to_json(filtered_data_filepath, orient='records')
そうすると、
[{"double":0,"single":0,"square":0},{"double":2,"single":1,"square":1},{"double":4,"single":2,"square":4},{"double":6,"single":3,"square":9},{"double":8,"single":4,"square":16},{"double":10,"single":5,"square":25},{"double":12,"single":6,"square":36},{"double":14,"single":7,"square":49},{"double":16,"single":8,"square":64},{"double":18,"single":9,"square":81}]
と保存された。
JupyterLabでアニメーションが動作しないときの対処
背景・目的
最近、深層強化学習を勉強していて、教材としては
https://www.amazon.co.jp/dp/4839965625
を使っている。 コードはノートブックに記述していて、JupyterLabを利用している。 Toy problem として自作で3×3マスの迷路を自作して、選択した行動をアニメーションとして描画するところで問題があったのでメモしておく。
エージェントが通るパスをアニメーションとして表示するコードを部分的に抜粋して載せておく。
from matplotlib import animation from IPython.display import HTML %matplotlib inline def init(): line.set_data([], []) return (line,) def animate(i): state = state_history[i] x = (state % 3) + 0.5 y = 2.5 - int(state / 3) line.set_data(x, y) return (line,) anim = animation.FuncAnimation(fig, animate, init_func=init, frames=len(state_history), interval=200, repeat=False) anim.save("maze.gif", writer = "imagemagick") HTML(anim.to_jshtml())
state_historyにはエージェントが通るパスのが保存されていて、それをアニメーションとして動かす。
今回問題になったは HTML(anim.to_jshtml())
の部分である。
アニメーションとして動かすには
Jupyter内で図をアニメーションとして動かすには、 matplotlib
の animation
を利用すると簡単に実現できる。
実際に、書籍では恐らくJupyter Notebookで実験することを想定しており、簡単にアニメーションを動かすことができた。
しかし、JupyterLabではJavaScriptの問題のためかアニメーションを再生することができない。
この問題はGithub上でissueとして上がっている。
ここでは、animationのオブジェクトにある to_jshtml()
を使っているが、
その他にも to_html5_video()
関数があり、これを使ってもアニメーションを再生できなかった。
どうやって解決したか
JupyterのNotebookはコードのみでなく、Markdownとして文章を記述することが可能である。 今回は、アニメーションをgifとして保存し、そのファイルをMarkdownで読み込みアニメーションを再生することで回避した。 上記のコードの中の
anim.save("maze.gif", writer = "imagemagick")
でアニメーションをgifとして保存している。 ここで保存したファイルをMarkdownで
<div class="pull-left"> ![Animated GIF](maze.gif) <div>
と記述した。
これにより、Jupyter NotebookでもJupyterLabでもアニメーションを表示することができた。
難点としては、アニメーションが無限にリピート再生されてしまうところくらいかなと思っている。
それよりも簡単に可視化されどのような挙動になるのか、可視化されながら説明できる点のほうがはるかに重要だと思うので、
回避方法が見つかってよかった。
おなじ問題に出くわしている人の解決方法になれば幸いです。
また、JupyterLabで to_jshtml()
や to_html5_video()
などコードでアニメーションが実現できた人はコメントなどでぜひとも教えてください!
Kerasで学習したモデルをWebAPIとして提供する
機械学習で学習済みモデルの予測結果をどうやって返すべきか勉強したので、WebAPIとして提供する場合のコードのメモする。 リポジトリはこちら。
個人的な学びポイントは、
load.py
のinit
関数でリロードした学習済みモデルを返している部分- 画像データの受け取り方
の2つである。
まず、学習済みモデルを返している部分についてだが、
tensorflow の場合、 Saver などを使い、 graph に学習済みモデルを読み込む必要があるが、
Keras の場合は学習済みのモデルをリロードし、 predict
関数を使って推論が行える。
そして、もう一つの画像データの受け取り方について。
今回、画像を DataUrl
という形式に変換して渡している。
これを利用することで、 API 側では画像を base64 の文字列として受け取ることができ、
画像として復元し、推論可能なデータサイズにリサイズすることで予測を行っている。
Flask を利用すれば非常に簡単に推論結果を返すことができるので、 モデルを作成して推論結果を検証するためのインターフェイスとしては効率の良い方法の一つではないかと思う。