GiNZA 2.0 を触ってみた。
GiNZA はmegagonlabs が開発している日本語の形態素解析器です。 フレームワークとしてspaCy をFramework として利用しています。 今日2.0 がリリースされたので、インストールとユーザ辞書について触っていきたいと思います。
前提
今回、pyenv でPython 3.7.3 をインストールした環境でのコードを書いていきます。
インストール手順
インストール手順はこちらのページにあります。
2通りのインストール方法があるみたいです。
1つは、圧縮ファイルをダウンロードして、そのあとpip でインストールする方法です。
そして、もう一つはリンクをpip インストールコマンドで指定する方法です。
今回は、後者の方でインストールをしてみました。
pip install "https://github.com/megagonlabs/ginza/releases/download/latest/ginza-latest.tar.gz"
この場合、 latest
がダウンロードされるみたいです。
バージョンを指定する場合は、 latest
を指定すれば良さそう。
形態素解析
import spacy nlp = spacy.load('ja_ginza') doc = nlp('依存構造解析の実験を行っています。') for sent in doc.sents: for token in sent: print(token.i, token.orth_, token.lemma_, token.pos_, token.tag_, token.dep_, token.head.i) print('EOS')
実行結果はこちらになりました。
0 依存 依存 NOUN 名詞-普通名詞-サ変可能 compound 2 1 構造 構造 NOUN 名詞-普通名詞-一般 compound 2 2 解析 解析 NOUN 名詞-普通名詞-サ変可能 nmod 4 3 の の ADP 助詞-格助詞 case 2 4 実験 実験 NOUN 名詞-普通名詞-サ変可能 obj 6 5 を を ADP 助詞-格助詞 case 4 6 行っ 行う VERB 動詞-一般 ROOT 6 7 て て SCONJ 助詞-接続助詞 mark 6 8 い 居る AUX 動詞-非自立可能 aux 6 9 ます ます AUX 助動詞 aux 6 10 。 。 PUNCT 補助記号-句点 punct 6 EOS
恐らく、 token.i
が出力のインデックス、 token.orth_
が表層、 token.lemma_
が原型、 token.pos_
が英語での品詞、 token.tag_
が日本語での品詞と活用のようです。
token.dep_
、 token.head.i
はよくわかっていないので、後日調べて記事にしたいですが、
token.head.i
は係り受け先の id
のように見えます。
ユーザ辞書
例えば 足利義満は金閣寺を建立しました。
のような文章を形態素解析をしたい場合に、以下のような結果になります。
0 足利 足利 PROPN 名詞-固有名詞-人名-姓 compound 1 1 義満 義満 PROPN 名詞-固有名詞-人名-名 nsubj 6 2 は は ADP 助詞-係助詞 case 1 3 金閣 金閣 NOUN 名詞-普通名詞-一般 compound 4 4 寺 寺 NOUN 接尾辞-名詞的-一般 obj 6 5 を を ADP 助詞-格助詞 case 4 6 建立 建立 VERB 名詞-普通名詞-サ変可能 ROOT 6 7 し 為る AUX 動詞-非自立可能 aux 6 8 まし ます AUX 助動詞 aux 6 9 た た AUX 助動詞 aux 6 10 。 。 PUNCT 補助記号-句点 punct 6 EOS
このようにデフォルトの辞書で形態素解析を行うと 金閣寺
がまとまった名詞として扱われることを期待していた場合に、異なった結果になる場合があります。
このような場合にユーザ辞書を利用します。
GiNZA は SudachiPy
を利用しているようなので、ユーザ辞書は SudachiPy
の書き方に従って書けばユーザ辞書が使えそうです。
ユーザ辞書を扱うまでをブログの内容にしたかったのですが、 次回にまわしたいと思います。
Pandas DataFrame のX軸の単位を調整する
内容
DataFrame で histogram を描画したときの x, y 軸のスケールの表示を調整する方法について、ハマったのでメモとして残しておきます。
TL;DR
DataFrame から直接 df.hist
とするのではなく、
一度、 df.plot
で matplotlib.axes._subplots.AxesSubplot
を取り出し、
その matplotlib.axes._subplots.AxesSubplot
の ticklable_format
の style
オプションを変更することで整数表示や浮動小数点表記での表示などを指定することができます。
ax = sample_df.plot(figsize=(15, 5), kind='hist', bins=100) ax.ticklabel_format(style='plain', axis='x') # 浮動小数点表記で表示したい場合、 style='sci' とする
Pandas の version
0.24.1
を使ってます。
import pandas as pd pd.__version__ '0.24.1'
どういう事象?
元々、データ分析の案件で発生した事象で、
少数を含む、かつ、取りうる値の範囲が広いデータを DataFrame で扱い、それを df.hist
で描画しようとしました。
今回は、だいたいのデータとして、以下のデータで再現実験をします。
sample_size = 10000 sample_data = np.random.rand(sample_size) sample_data = [d * np.random.randint(low=1, high=10000000) for d in sample_data] sample_category = ["a_{}".format(i) for i in range(sample_size)] d = {'col1': sample_category, 'col2': sample_data} sample_df = pd.DataFrame(d)
以下のようなデータが作成されます。
pd.options.display.float_format = '{:.2f}'.format sample_df.head(10) col1 col2 0 a_0 6809432.40 1 a_1 3095277.45 2 a_2 1515776.87 3 a_3 6291255.66 4 a_4 429976.20 5 a_5 422715.54 6 a_6 5695883.51 7 a_7 2793741.89 8 a_8 2343652.35 9 a_9 4571542.77
分布としては、以下のような感じ。
sample_df.describe() col2 count 10000.00 mean 2498833.72 std 2212640.08 min 128.83 25% 662876.83 50% 1854733.14 75% 3875713.08 max 9861186.18
桁のオーダを見ると 102 から 106 のデータが生成されています。
このデータを df.hist
で描画すると
のような形式になりました。 X 軸のスケールに注目すると自動的に浮動小数点表記に変換されていることがわかります。
今回のようなランダムに生成されたデータの場合、このような表現でもわかりますが、
この結果を他の方にシェアする場合、実際の値としてみた方が感覚をつかみ安い方もいるかと思います。
どうやって整数表示にするか
x, y 軸の表示に関する操作はどうやら matplotlib.axes.Axes
class にて操作することは調べて想定がつきました。
ただ、問題なのは、 df.hist
で返される値の型は numpy.ndarray
でした。
hist = sample_df.hist(bins=100, figsize=(15, 5)) type(hist) numpy.ndarray
なので、 matplotlib.axes.Axes
class を操作しようとしても操作できず、
軸のスケールを操作することができません。
なので、 histogram の描画を df.hist
で行うのではなく、
df.plot
で描画し、 option の kind で kind='hist'
とすることで、 matplotlib.axes.Axes
class を戻り値として受け取れるようにしました。
ax = sample_df.plot(figsize=(15, 5), kind='hist', bins=100) type(ax) matplotlib.axes._subplots.AxesSubplot
これにより、目的だった matplotlib.axes.Axes
class を操作することができました。
matplotlib.axes.Axes
class の ticklabel_format
で style='plain
とすることで軸のスケールを整数表示することができました。
ax = sample_df.plot(figsize=(15, 5), kind='hist', bins=100) ax.ticklabel_format(style='plain', axis='x') # axis='y' とすると y 軸のスケールを整数にすることができる
反対に、明示的に浮動小数点表記としたい場合は、 ticklabel_format
の style オプションを style=sci
とすればよく、
y 軸のスケールを変えたい場合は、 ticklabel_format
の axis オプションを axis=y
とすればよいみたいです。
Pandas DataFrame で圧縮したデータファイルを読み込む
概要
圧縮されたファイルを Pandas DataFrame で読み込むための手順を紹介する。
毎回解凍してから読み込みをしていたのを、圧縮したまま読み込めたので、忘備録として残す。
今回は gzip 圧縮した前提で記載します。
内容
すごくシンプルだったので、まずコードを載せます。
import pandas as pd gz_file_path = 'file.tsv.gz' df = pd.read_table(gz_file_path, compression='gzip') df.head()
gzip 圧縮したファイルのパスを gz_file_path
に格納しています。
その後、 pd.read_table
で読み込みをしています。
その際に compression
オプションで圧縮フォーマットを指定するだけで、
読み込んでくれます。
自分の手元では、 header に column 名が載っているデータを読み込ませたところ、 df.head()
で DataFrame の先頭の5件を表示させたときに header 行の内容が表示されていました。
もし、 header 行が無いデータの場合は、 names
オプションにカラム名を指定すればよい。
扱える圧縮のファイル形式
扱えるフォーマットは、
- gzip
- bzip2
- zip
- xz
がある。
compression
オプションで圧縮フォーマットを指定しない場合、
ファイルの拡張子から圧縮フォーマットを推定してくれるみたいだが、
明示的に記載している方がよいと思われる。
OSS に Contribute しました
簡単な変更でしたが、OSS に Contribute しました。
内容と所感
内容はドキュメントに favicon.ico を追加しただけという…笑
実は OSS にコミットという点で言えば、初めてのコミットだった。
しかも、今日初めて読むプロジェクトに Sphinx という不慣れなフレームワークでの修正。
なので、小さな変更だがすごく達成感があり、気持ちとしては晴れ晴れしている。
元々は OSS にコミット、さらに言えば、機械学習系の OSS にコミットしていきたいという気持ちがあった。
なので、今回の変更は小さいけれど、これをきっかけにもっとコミットしていきたいと思っている。
Good first issue
そもそもなんで OSS に Contribute したいと思っていたのに、できなかったか。
正直、OSS にコミットしたくていろいろ資料よんだけど、
結局のところ、何すれば良いのか、というのがわからなかったというのが大きかった。
そんななか、この資料を読んだ。
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
ElasticSearch に Bulk Insert をする
以前の記事でも記載したが、本業のプロジェクトで ElasticSearch を利用したシステムの構築をしようとしている。
Docker で作ったデモ環境では、 Embulk での bulk insert ができていた。
しかし、 AWS Elasticsearch Service では embulk-output-elasticsearch がサポート外のため、利用できなかった。
なので、別の手段で bulk insert を実装した。
Python の ElasticSearch Client を使う
コードは Python をベースで利用する。
なので、 ElasticSearch の Client として、
Python Elasticsearch Client — Elasticsearch 6.3.0 documentation
を利用する。 インストールは、
pip install elasticsearch
でインストールできる。
基本的には、インストールする Client のバージョンは、 ElasticSearch インスタンスのメジャーバージョンに合わせて利用するのが推奨されていそう。
なので、例えば 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 で起動する方法を紹介した。
しかし、 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 にお願いします!