OSS に Contribute しました

簡単な変更でしたが、OSS に Contribute しました。

github.com

内容と所感

内容はドキュメントに favicon.ico を追加しただけという…笑
実は OSS にコミットという点で言えば、初めてのコミットだった。
しかも、今日初めて読むプロジェクトに Sphinx という不慣れなフレームワークでの修正。
なので、小さな変更だがすごく達成感があり、気持ちとしては晴れ晴れしている。

元々は OSS にコミット、さらに言えば、機械学習系の OSS にコミットしていきたいという気持ちがあった。
なので、今回の変更は小さいけれど、これをきっかけにもっとコミットしていきたいと思っている。

Good first issue

そもそもなんで OSS に Contribute したいと思っていたのに、できなかったか。
正直、OSS にコミットしたくていろいろ資料よんだけど、
結局のところ、何すれば良いのか、というのがわからなかったというのが大きかった。

そんななか、この資料を読んだ。

speakerdeck.com

OSSリポジトリをみる時に、ざっくり issue を読んでいた。
しかし、上記の資料を見た時、どの issue に注目すれば良いかわかったので、
今回のような些細だが Contribute するきっかけを得られたと思っている。

今後

引き続き、 blueoil は引き続き注目して ML の範囲で Contribute できるように挑戦していきたい。

Python で標準出力をリアルタイムに出力する方法

Jenkinsで実行ログをリアルタイムで確認したい

Jenkis ではジョブの実行ログをリアルタイムに確認することができる。
しかし、 ( Python ではスクリプト言語のため)、処理を高速化するために標準出力をバッファしてまとめて出力する。
なので、 Jenkins でジョブの動作を確認する場合、まとまった単位で標準出力のログが出力される。
毎日、定時に実行するようば定例バッチの場合はそれでもいいが、
開発途中の場合、リアルタイムにログがみたい場合があると思う。
そのような場合の忘備録を残す。

Pythonでリアルタイムに標準出力するには

Pythonでは使うバージョンによって、即時標準出力する方法が異なる。

Python3.3以降

これは有名な方法だが、 flush オプションを True で設定する。

print("プリントする文言", flush=True)

Python3.3より前 + Python2.X 系

2020年には Python 2系はメンテされなくなると思うが、一応載せておく。
sys.stdout.flush() を使うことで flush される。

import sys
print("プリントする文言")
# Python 2系なら
# print "プリントする文言"
sys.stdout.flush()

いちいち仕込むべきなのか

冒頭で書いたように、バッチの性質によって必ずしも常に flush する必要はないと思う。
しかし、リアルタイムに出力させたい場合に備えて、 flush を仕込む方針を取ると非常に厄介だと思う。
例えば、今ブログを書きながらで思いつく方法としては、
バッチ実行時に flush をするかどうかの判定を挟む方法だ。
例えば、環境変数に flush_flg を仕込んでそれを使って判定する、のような処理。
この場合、個人で開発している場合は統一できるかもしれないが、時間の経過と共に忘れてしまったりするし、 チームで開発している場合、人それぞれの書き方や基準があやふやになって推奨できない。 また、そのために共通関数化するのもたかが標準出力のためにバカらしい。 不用意な変数を仕込むのでバグの温床にもなりかねない。 なので、できるならシンプルな形式に留めるほうが良いと思う。

Python のオプションを使う

Python のオプションに -u オプションがある。
python -h とすると用途が見られる。 以下に出力を抜粋する。
ちなみに自分が使っているバージョンは Anaconda の Python 3.6.5 である。

$ python -V
Python 3.6.5 :: Anaconda, Inc.

$ python -h
... (抜粋)
-u     : force the binary I/O layers of stdout and stderr to be unbuffered;
         stdin is always buffered; text I/O layer will be line-buffered;
         also PYTHONUNBUFFERED=x
...

とある。
要約すると、標準出力とエラー出力を強制的にバッファしないようにする。とのこと。
つまり、このオプションを使えば flush と同じことができる。
なので、例えば開発環境などで随時標準出力を見たい場合は、 Jenkins のジョブ実行スクリプト画面で

python -u <実行するスクリプト>

すればよいし、そのまま定例実行に切り替えるときは、

python <実行するスクリプト>

とするだけでよい。

FYI

Python2 count down

https://pythonclock.org/

ElasticSearch に Bulk Insert をする

以前の記事でも記載したが、本業のプロジェクトで ElasticSearch を利用したシステムの構築をしようとしている。
Docker で作ったデモ環境では、 Embulk での bulk insert ができていた。 しかし、 AWS Elasticsearch Service では embulk-output-elasticsearch がサポート外のため、利用できなかった。

github.com

なので、別の手段で bulk insert を実装した。

Python の ElasticSearch Client を使う

コードは Python をベースで利用する。
なので、 ElasticSearch の Client として、

Python Elasticsearch Client — Elasticsearch 6.3.0 documentation

を利用する。 インストールは、

pip install elasticsearch

でインストールできる。
基本的には、インストールする Client のバージョンは、 ElasticSearch インスタンスのメジャーバージョンに合わせて利用するのが推奨されていそう。

pypi.org

なので、例えば ElasticSearch の 5.X.X を利用している場合、インストールする Client は

pip install elasticsearch>=5.0.0,<6.0.0

でインストールすることで、5系の中の最新バージョンの Client をインストールすることができる。

bulk insert

ElasticSearch に bulk insert する場合、 helpers の中の bulk method を利用することで bulk insert が実装できる。
参考として、以下に 1000 件のデータに対して bulk insert するコード例を載せる。

from elasticsearch import Elasticsearch, helpers

es = Elasticsearch(host='localhost', port=9200)

data = []

for i in range(1000):
    doc = {'hoge': 'foobar'}
    data.append(
        {'_index':'hoge-index', '_type':'hoge-index-type', '_source':doc}
    )

helpers.bulk(es, data)

100件などの単位で分割して insert したい場合、 for ループ内で適当な件数で bulk method を呼び出すようにすれば良い。

感想

Embulk で insert するのもとても楽だったが、Elasticsearch の helpers を利用して簡単に bulk insert が実装できた。
Elasticsearch の helpers には他にも便利な method が多くあるのでやりたいことを helpers 内の method で実現できるかを考えたほうが良さそうだなと思った。

DockerCompose で ES + Kibana 環境を構築しました!

前回の記事で ElasticSearch と Kibana の両方を Docker で起動する方法を紹介した。

toohsk.hateblo.jp

しかし、 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 上にリポジトリを作成しました。

github.com

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 をビルドする。
なので、ここの valuedocker-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

f:id:toohsk:20180903203020p:plain

できた。

所管

恐らく本当は 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でカンマ区切りのデータを展開した記事はよく出てくる。

qiita.com

これを見たときは、『そっか、じゃあ 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

発表資料はこちら。

speakerdeck.com

副業をしていて感じていることを話してきた。

まだ初めて数ヶ月だが、いい振り返りになった。
視座を高めるために知識の input だけでなく、 output を行い経験値を増やすこと。
そのサイクルを回すことで自身の視座を高くしていく努力をしていくこと。
それを再認識できた発表だった。

懇親会であった質問

ものすごくありがたいことに参加者から、『発表が良かった』と言ってくださる方が多くいらっしゃった。
その方々といろいろ話すことができたので、質問と自分の考えをまとめておく。

始め方はどうされたのですか?
僕の場合、スライドに載せているFISMという会社のCTOをしている @kzkohashi と同僚であり、 そのつながりで始めることができた。 元々興味はあったが、なかなか始める決意がつかなかったが、とてもいいきっかけをくれたと思っている。

いいなと思っている会社があり、そこで副業できればなと思っていますが、どう思いますか?
副業として受け入れてくださるのであれば、積極的に取り組めばいいと思っている。
正直、外から見ていていいなーと思う会社はいっぱいある。 ただ、入ってみて、あれ?と思うこともある。 そうなってしまったときに、現職を退職してまでって後悔がすごく、これが現職にとどまり続ける要因の一つなのかと思う。
そういう意味では副業として参加し、正社員と肩を並べて働いてみて、直に会社の文化に触れることはいいスタートの切り方なのではないだろうか? そして、思ったより肌に合わないと判断できた場合は、信頼を稼いでつながりを構築することに専念すればいいと思っている。

アウトプットの仕方どうしていますか?
データ分析の場合、データを分析して最後の可視化と考察の部分が重要だと思っている。
結局のところ期待されているのはアウトプットであり、例えば、

  • 必要なタスクに対して実現可能なのか不可能なのか
  • 不可能な場合、だいたい手段はあるのかもっと作業を進めれば可能性はでてくるのか

などが期待されているのではないだろうか。
データ分析といえばデータの前処理が重要であることは重々承知している。 しかし副業だと、発注者側にデータ分析を理解してくれる人がいない場合、 最初から前処理をコツコツやっていても、「何してるんですか?」ってなると思う。
それよりも、 「既存のデータを使ってこうしました。ここの部分の改善をすることで精度があがると思うので、次にここを着手します。」 と道筋を見せて上げたほうが納得感が得られるし、自分自身必要な処理が見えてくるはずである。
なので、アウトプットを細かく見せることはすごく重要だと考えている。

ポートフォリオはどうされていますか?
ここで悩んでいる人が多かった印象だった。
正直、自分は知り合いの相談から副業がスタートしているので、ポートフォリオとかを考えなくても良かった。 しかし、もっといろんな会社の案件を触ってみたいなと思ったときに、ポートフォリオをつくらないとなとなった。
恐らく、ポートフォリオとして上がるのは、「エンジニアならgithubだろ!」というのが定石なのではないだろうか。 その声がすごく多かったし、それには賛成である。
しかし、機械学習のエンジニアはデータがないとなかなかアピールポイントがないので、 githubにコードを上げることは難しいのではないかと思う。
そこで僕はブログもセットでポートフォリオを組むといいのではないかと思っている。
データ前処理の中で得られた気づきは、大抵、gistやgithubに上げるほどではない。 だが、その一部をブログとして残すことで自分がどの程度データに触れているのかというのをアピールすることに繋げられる。
積もり積もればれっきとした履歴書であるし、コード以外の考えをアピールすることができるのでよいと思う。 ブログのアウトプットについては @kakakakku のブログに対する考え方は参考になるので、 一度は読まれたほうがいいと思う。

kakakakakku.hatenablog.com

最後に

今回は本当に貴重な会で登壇させていただけた。 今後もいろんな勉強会に登壇できるよう、自分の成長に繋げられるように input/output を引き続きがんばろう。