Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

トピックモデル(LDA)で初学者に分かりづらいポイントについての解説

こんにちは。
信号処理で使っていた数学の知識を生かして、
機械学習関連の仕事をしている2年目の@です。

こちらは機械学習と数学 Advent Calendarの11日目の記事となります。

qiita.com

トピックモデルの学習で初学者に分かりづらいポイントについての解説をしていきます。

機械学習における数学の重要性

機械学習を利用してアプリケーションを作る際に、数学の知識は重要です。
機械学習の便利なライブラリは多くリリースされていますが、適切に使用するにはパラメータチューニングが必要だったり、
最新の手法を動かしたい場合は自分で数式を読んで理解し、開発しないといけません。

というわけなので、数学は大事です。機械学習でアプリケーションを作るみなさん数学を勉強しましょう。

トピックモデル

トピックモデルとは何か

トピックモデルは潜在的意味を推定するためのモデルです。

トピックモデルのゴールの1つとして、潜在的な意味を得ることがあります。
潜在的意味の例としては、ニュース記事のカテゴリとか、画像に何が映っているかとか、音声の音程とかです。

潜在的意味解析

潜在的意味をトピックと定義して、各トピックに応じたデータを生成する仕組みがあると仮定しています。

Latent Semantic Indexing(LSI), Latent Dirichlet Allocation(LDA)などの手法がありますが、
ここではLDAについて書きます。
LDAでは、多項分布とディリクレ分布によって各データが生成されていると仮定しています。
そして、学習データから各データが生成される仕組み(生成過程)を学習します。
文書を例にすると、「スポーツ」をトピックとして持つ文書は「ボール」などの単語から生成されている
のような「仕組み」が学習されるわけです。この「仕組み」は後述にもあるディリクレ分布や多項分布でも紹介します。

この生成過程の学習により、データのカテゴリ分けやデータの生成を行えます。

LDAやその派生により、文書のカテゴリ分けや、音声データの認識の応用に使われています。
しかし、初学者には難しく、私も勉強していて苦労しました。
そこで、自分にとって分かりづらかった部分の解説をしていきます。

分かりづらいポイント

本記事では、分かりづらいポイント2点紹介します。

1. LDAで仮定されるデータの生成過程のイメージ
 データの生成過程で、確率分布の実現値を使った計算が出てきます。
 この部分は確率を使用するような文献をあまり読んでこなかった身としては、イメージがつきませんでした。
2. データ生成過程でなぜ多項分布やディリクレ分布を使用するのか
 多項分布やディリクレ分布の定義を見ると実現値がどのような値になるのかはわかるのですが、
 それが、実際のデータにおいてどの部分にあたるのかが分かりづらいと感じました。

1. データの生成過程のイメージ

文書データを例にして説明します。
文書のトピック種別がK個で決まっていて、文書dn_d個の単語w_iからできている場合の
データ生成の過程は図1のようになります。
f:id:acro-engineer:20171210213627p:plain

[図1 LDAのデータ生成過程]

各記号の意味は次の通りです。
\alpha:文書のトピック割合を生成するディリクレ分布のパラメータ
\theta_d:文書dに各トピックkが登場する確率を表すベクトル(成分の和が1)
z_{d,k}:文書dに登場するトピックkの単語数
\beta:トピックの単語生成確率を生成するディリクレ分布のパラメータ
\phi_k:各トピックでの単語w_iが生成される確率を表すベクトル(成分の和が1)
x_{d,i}:文書dに登場する単語w_iの個数

LDAではまず、文書を単語の集まりとみています(単語数だけしか見ていない)。
「文書を書く」行為はどのようなトピックかを予め決めて、空の文書にM種類ある単語の中から
1つずつ選んで追加していく動作と考えられます。

この行為によって選ばれた合計でn_d個の単語は、n_d
単語の登場確率\phi_kをパラメータとした多項分布の実現値(=実際に選んだ値)とみなせます。

LDAでは、予め決定するトピックの単語数もn_dと文書のトピック割合\theta_d
パラメータとした多項分布の実現値とみなしています。まずは、多項分布について説明します。

多項分布

1回でM種類の値からどれか1つを選ぶという試行があり、それをn_d回繰り返すときに、
M種類の値がそれぞれ出現する回数を表現するベクトルは多項分布に従います。

パラメータとして、試行回数n_dM種類の値がそれぞれ出る確率pを持つ。
確率変数X=(x_1, \ldots, x_M)n_dp=(p_1, \ldots, p_M)をパラメータとする多項分布に従うとき、確率質量関数f(x_1, \ldots, x_M; n, p_1, \ldots, p_M)
f(x_1, \ldots, x_M; n, p_1, \ldots, p_M)=\left\{ \begin{array}{ll}
    \frac{n!}{x_1! \cdots x_M!}p^{x_1}_1 \cdots p^{x_M}_M & \Bigl(\sum^{M}_{m=1}x_m=n_d\Bigr) \\
    0 & (otherwise)
  \end{array} \right. \qquad where \ \sum^{k}_{i=1}p_i=1
となります。
例えば、x_1は単語1を追加した個数、p_1M種類の単語から単語1を選ぶ確率です。

多項分布では、それぞれの値が出る確率と試行回数がパラメータとなっています。
さて、今度はこのパラメータを決めることが必要です。
それぞれの値が出る確率はディリクレ分布から生成しています。

ディリクレ分布

単体(※1)上の確率分布で、長さNのベクトルをパラメータとして持っています。
※1.成分の和が1になる長さNのベクトルを集めた集合

確率変数X=(x_1, \ldots, x_k)\alpha=(\alpha_1, \ldots, \alpha_k)をパラメータとするディリクレ分布に従うとき、確率密度関数g(X; \alpha)
g(X; \alpha)=\frac{\Gamma(\sum^{k}_{i=1}\alpha_i)}{\prod^{k}_{i=1}\Gamma(\alpha_i)}\prod^{k}_{i=1}x^{\alpha_i-1}_{i} \qquad where \ \sum^{k}_{i=1}x_i=1 \ \mathrm{and} \ \forall i \in \{1, \ldots, k\} \ \alpha_i>0, x_i \geq 0
となる。
例えば、x_1はある文書中でトピック1の単語が登場する確率になります。

ディリクレ分布の実現値から成分の和が1になるベクトルが得られるため、
このベクトルを多項分布のパラメータp=(p_1, \ldots, p_M)として使用します。

このように、ディリクレ分布によって多項分布のパラメータを設定するため、
Latent Dirichlet Allocation(LDA)という名前がついているようです(※2)。
※2.潜在的(Latent)なパラメータをディリクレ分布(Dirichlet)によって割り当てている(Allocation)。

まとめると、文書データの生成は次のように行われます。

1. 文書のトピック割合をディリクレ分布から生成する・・・①
2. 各トピックの単語選択確率をディリクレ分布から生成する・・・②
3. 文書の単語数と①をパラメータとした多項分布から、トピックごとの単語数を生成する・・・③
4. ③と②から各単語の使用回数を生成する → 文書データと等しい。

2. なぜ多項分布やディリクレ分布を使用するのか

LDAでは、「文書を書く」過程を単語選択の連続と考えているため、
M種類の値から1つ選ぶ動作を繰り返した結果としての多項分布の実現値を使用しています。

ディリクレ分布については、多項分布のパラメータを得るために使用していますが、
他の分布からでもよいのではないかと思われるかもしれません。

なぜディリクレ分布なのかという部分について完全には理解していませんが、
理由の1つとしてパラメータ推定を行う際に計算を簡単にするためというものがあります。
ディリクレ分布は多項分布の共役事前分布となっているため、計算上都合がいい場面が多くあります。

まとめ

トピックモデルでは他の手法と違い、データをどう分類するかを学習するのではなく、
分類先からどのようにデータが生成されるかの仕組みを学習するという考え方となっています。
その仕組みを数学的に表現するために、多項分布だったり、ディリクレ分布だったりが登場しています。

引き続き、パラメータの学習(最適化問題を解く部分)についても勉強していきます。

Acroquest Technologyでは、キャリア採用を行っています。


  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

【データ分析】
データ分析案件で時系列データの異常検知に挑戦したいエンジニアWanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

#Elasticsearch の cat APIs にまじめに入門する話

こんにちは、 @ です。

こちらはElastic stack Advent Calendarの9日目の記事となります。
qiita.com

Elasticsearchを構築・運用する際に頻繁に使うことになる cat APIsについて、その使い方をちゃんと知ってほしいとプロジェクトのメンバに思うことが増えてきましたので、アドベントカレンダーというイベントの力を借りてまとめておこう思います。

ことの起こり(スタート地点)

メンバに「cat indices でインデックス情報とって後でみせて」とお願いしたところ、次のような情報をもらいました。

yellow open  .monitoring-es-6-2017.12.09         EjmyMwfUR9GC7NpGE9J-Ew 1 1   339106 5678  226.7mb  226.7mb
green  open  report_attachment-2017.11.21        Z8Cptg_URveTYojN-PUeDg 1 0       66    0    1.1mb    1.1mb
yellow open  .watcher-history-3-2017.11.08       4yE6p3yrQ4WbRDr8Z6CHKw 1 1      660    0  617.2kb  617.2kb
green  open  report_attachment-2017.11.17        _t0M6p3HSj2SHX3TAraDtg 1 0      141    0    2.6mb    2.6mb
       close report_data-2017.11.13              6KO-h7faTU2_e472PEjwSQ                                    
green  open  report_attachment-2017.12           iE_4F5LLRISLuNIoXENCtg 1 0      154    0    2.4mb    2.4mb
yellow open  .watcher-history-3-2017.12.06       bnamUII8RHW0yqMtWS8xFg 1 1     7200    0    6.1mb    6.1mb
yellow open  .watcher-history-3-2017.12.05       GVDLk_p9T_6Mo2UNPw96PQ 1 1     7199    0    6.2mb    6.2mb
green  open  report_attachment-2017.11.14        WY8F_XBbRzKUqKUIh_fXrw 1 0       56    0      1mb      1mb
yellow open  .watcher-history-3-2017.10.26       bSAcokr1RsezlYVJTyzQQg 1 1     7200    0    5.8mb    5.8mb
yellow open  .watcher-history-3-2017.12.04       PkU3LewJR46a68dA8WUm1w 1 1     7200    0    6.1mb    6.1mb
yellow open  .watcher-history-3-2017.12.03       df6rtiv2SGKTWoP_G9S-8A 1 1     7200    0    6.1mb    6.1mb
yellow open  .watcher-history-3-2017.11.29       6HmPCZt8QUuMPeeKrZSa5w 1 1     7076    0    6.2mb    6.2mb
(略)

確かにお願いした通りの情報です。確かに。しかし・・・

  • 右から3番目のカラムって何の値だっけ?最後の2つのカラムの違いはなんだっけ?
  • データサイズがgb、mb、kbとなっているのは目視ではよいけど、計算したいときはどうしてくれる?
  • 最初からソートしてくれれば見やすいのに

などなど、実際にこの情報からあれこれ判断しようとすると情報が足りなかったり、データの取り回しが面倒だったりとで、困ってしまいました。

そこで、かく言う私自身も cat APIs をラフに使っていただけで、何ができるのか分かった上で頼んでいなかったことを反省し、一度ちゃんと何ができるのか調べることにしました。

ちゃんと cat indices の公式ドキュメントを読んでみる

そこでまずは、次の cat indices のドキュメントを読んでみましょう。

www.elastic.co

最初に出てくるのは次の例です。

GET /_cat/indices/twi*?v&s=index

こちら参考にして、次のように実行してみました。

GET _cat/indices?v&s=index
health status index                               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   .kibana                             pKQqx9GIQc-18XXQfZ9izQ   1   1         76            2    192.9kb        192.9kb
yellow open   .monitoring-alerts-6                ctx5fAJvRPyyrvQ5_2r90g   1   1          1            0     12.5kb         12.5kb
yellow open   .monitoring-es-6-2017.12.07         -T2tZg7mQASYINKzSglq-w   1   1    1036171         6130    620.7mb        620.7mb
yellow open   .monitoring-es-6-2017.12.08         350uCr70RqqxVQdAaa6dSA   1   1    1014396         6098      600mb          600mb
yellow open   .monitoring-es-6-2017.12.09         EjmyMwfUR9GC7NpGE9J-Ew   1   1     362824         6482    254.3mb        254.3mb
yellow open   .monitoring-kibana-6-2017.12.07     8vCosLHYRHmTplPwk5I34w   1   1       8633            0      3.5mb          3.5mb
yellow open   .monitoring-kibana-6-2017.12.08     1Ksw2PkOSsmh9beMxFg08g   1   1       8635            0      3.3mb          3.3mb
yellow open   .monitoring-kibana-6-2017.12.09     YxxKy594RAujnUBH-kwyqw   1   1       3043            0      1.3mb          1.3mb
yellow open   .triggered_watches                  txzhs8dCTnOvduA51onZFQ   1   1          0            0      3.2mb          3.2mb
yellow open   .watcher-history-3-2017.10.25       DF1MLpV2RxGRu-ASeb0e1g   1   1       4510            0      3.7mb          3.7mb
yellow open   .watcher-history-3-2017.10.26       bSAcokr1RsezlYVJTyzQQg   1   1       7200            0      5.8mb          5.8mb

出力にカラム名が表示されて、index名でソートされて表示されるようになりました。そうですよね。それくらいありますよね。

そのまま読み進めていくとソートオーダーを変更したり、表示項目を指定したりできるようです。ですよね。ですよね。

しかし、このドキュメントの中で紹介されている表示項目のところで手が止まってしまいました。

GET /_cat/indices/twitter?pri&v&h=health,index,pri,rep,docs.count,mt

「mt ? 」

GET /_cat/indices?v&h=i,tm&s=tm:desc

「tm ?」

それぞれ、merge操作の数やindex毎のメモリ量が分かるようなのですが、そもそもどんなカラムが存在するのかその先を読み進めても書いていません。
そもそも「v」とか「s」とかも、カラムヘッダの表示、ソートだとは内容からは分かるのですが、その説明自体は書いてありません。
これはつまり、もう一段階上のレベルのドキュメントがあるということだと思い、次のドキュメントを見ることにしました。

cat APIs の公式ドキュメントを読んでみる

Elasticsearchには cat indices を始めたとした、たくさんのcat APIがあります。その全体の最初にある cat APIs のドキュメントを読んでみましょう。

www.elastic.co

そうすると、「私が悪かったです」と反省したい気持ちになりますが、ちゃんと

  • Verbose(v)
  • Headers(h)
  • Sort(s)
  • Help
  • Numeric formats
  • Response as text, json, smile, yaml or cbor

について書かれており、help を使うことで各APIにおいてどのようなカラムが存在するかを確認でき、データ量の表現形式も bytes を使って統一できることが分かりました。

cat indices の使い方のまとめ

というわけで 、ここまで調べてきたことを踏まえて、次のように使ったらよいよ、というのをまとめます。

事前準備

helpを使ってまずはどのようなカラムが取得できるのか確認しましょう。

GET _cat/indices?help
health                           | h                              | current health status
status                           | s                              | open/close status
index                            | i,idx                          | index name
uuid                             | id,uuid                        | index uuid
pri                              | p,shards.primary,shardsPrimary | number of primary shards
rep                              | r,shards.replica,shardsReplica | number of replica shards
docs.count                       | dc,docsCount                   | available docs
docs.deleted                     | dd,docsDeleted                 | deleted docs
(略)
merges.total                     | mt,mergesTotal                 | number of completed merge ops
pri.merges.total                 |                                | number of completed merge ops
(略)
segments.count                   | sc,segmentsCount               | number of segments
pri.segments.count               |                                | number of segments
segments.memory                  | sm,segmentsMemory              | memory used by segments
pri.segments.memory              |                                | memory used by segments
(略)
memory.total                     | tm,memoryTotal                 | total used memory
pri.memory.total                 |                                | total user memory

基本的な使い方

カラムヘッダの表示(v) 、表示したいカラムの指定(h) 、ソートオーダーの指定(s)を使いましょう。

GET _cat/indices?v&h=health,status,index,p,r,docs.count,docs.deleted,store.size,memory.total&s=docs.deleted:desc
health status index                               p r docs.count docs.deleted store.size memory.total
yellow open   .monitoring-es-6-2017.12.09         1 1     406248         6482    279.5mb        1.2mb
yellow open   .monitoring-es-6-2017.12.07         1 1    1036171         6130    620.7mb        2.6mb
yellow open   .monitoring-es-6-2017.12.08         1 1    1014396         6098      600mb        1.6mb
yellow open   .kibana                             1 1         76            2    192.9kb       23.8kb
green  open   report_data-2017.11                 1 0        144            1      1.6mb       27.2kb
green  open   report_data-2017.10.31              1 0          2            1     20.7kb       13.3kb
green  open   report_attachment-2017.11.21        1 0         66            0      1.1mb        6.6kb
yellow open   .watcher-history-3-2017.11.08       1 1        660            0    617.2kb       44.5kb
green  open   report_attachment-2017.11.17        1 0        141            0      2.6mb       18.8kb
green  open   report_attachment-2017.12           1 0        154            0      2.4mb        3.6kb
yellow open   .watcher-history-3-2017.12.06       1 1       7200            0      6.1mb       58.7kb
yellow open   .watcher-history-3-2017.12.05       1 1       7199            0      6.2mb      113.6kb
green  open   report_attachment-2017.11.14        1 0         56            0        1mb        4.5kb

これは削除フラグのついたドキュメント(docs.deleted)数を多い順に並べてみた例です。ソートの降順、昇順は desc 、 asc を付けることで制御できます。ソートに利用するカラムを複数指定することもできます。

数値のフォーマット指定

データサイズの量をMBで統一したい、というような時には bytes を指定しましょう。

GET _cat/indices?v&h=index,docs.count,docs.deleted,store.size,memory.total&s=docs.deleted:desc&bytes=mb
index                               docs.count docs.deleted store.size memory.total
.monitoring-es-6-2017.12.09             437754         6410        298            1
.monitoring-es-6-2017.12.07            1036171         6130        620            2
.monitoring-es-6-2017.12.08            1014396         6098        600            1
.kibana                                     76            2          0            0
report_data-2017.11                        144            1          1            0
report_data-2017.10.31                       2            1          0            0
report_attachment-2017.11.21                66            0          1            0
.watcher-history-3-2017.11.08              660            0          0            0
report_attachment-2017.11.17               141            0          2            0
report_attachment-2017.12                  154            0          2            0
.watcher-history-3-2017.12.06             7200            0          6            0
.watcher-history-3-2017.12.05             7199            0          6            0
report_attachment-2017.11.14                56            0          1            0

bytes の値には b, mb, gb などのように単位を指定できます。これまでとは異なりデータサイズの部分が数値のみになりました。これでExcelに貼ったときはそのまま計算できますね。

プライマリのみのデータサイズを表示する

priフラグを指定することで、プライマリのみのデータサイズを確認することができます。
priフラグを指定しない場合(デフォルト)ではレプリカも含んだ全体のデータサイズになっています。

GET _cat/indices?v&h=index,docs.count,docs.deleted,store.size,memory.total&s=docs.deleted:desc&pri
index                               docs.count docs.deleted store.size pri.store.size memory.total pri.memory.total
.monitoring-es-6-2017.12.09             439760         6482    299.8mb        299.8mb        1.1mb            1.1mb
.monitoring-es-6-2017.12.07            1036171         6130    620.7mb        620.7mb        2.6mb            2.6mb
.monitoring-es-6-2017.12.08            1014396         6098      600mb          600mb        1.6mb            1.6mb
.kibana                                     76            2    192.9kb        192.9kb       23.8kb           23.8kb
report_data-2017.11                        144            1      1.6mb          1.6mb       27.2kb           27.2kb
report_data-2017.10.31                       2            1     20.7kb         20.7kb       13.3kb           13.3kb
report_attachment-2017.11.21                66            0      1.1mb          1.1mb        6.6kb            6.6kb
.watcher-history-3-2017.11.08              660            0    617.2kb        617.2kb       44.5kb           44.5kb
report_attachment-2017.11.17               141            0      2.6mb          2.6mb       18.8kb           18.8kb
report_attachment-2017.12                  154            0      2.4mb          2.4mb        3.6kb            3.6kb
.watcher-history-3-2017.12.06             7200            0      6.1mb          6.1mb       58.7kb           58.7kb
.watcher-history-3-2017.12.05             7199            0      6.2mb          6.2mb      113.6kb          113.6kb
report_attachment-2017.11.14                56            0        1mb            1mb        4.5kb            4.5kb

試した環境(シングルノード)では差が出ませんが、「pri」が頭に付いた方がプライマリのみのデータサイズとなります。

indexを絞り込む

日単位のindexを作っていて、ある日だけのindexの情報がみたいというような時にindexを絞り込みたくなります。って、これは何も特別なことは不要で、単にindexパターンの指定をするだけです。

GET _cat/indices/*-2017.12.02?v&pri&h=health,status,index,p,r,docs.count,docs.deleted,store.size,memory.total&s=docs.deleted:desc
health status index                           p r docs.count docs.deleted store.size pri.store.size memory.total pri.memory.total
yellow open   .monitoring-es-6-2017.12.02     1 1      26905          112     17.9mb         17.9mb       88.8kb           88.8kb
yellow open   .monitoring-kibana-6-2017.12.02 1 1       1552            0      668kb          668kb         26kb             26kb
yellow open   .watcher-history-6-2017.12.02   1 1       1310            0        1mb            1mb       56.4kb           56.4kb

indexのhealthの状態で絞り込みたいときは、healthを使って絞り込みたい状態を指定します。

GET _cat/indices?v&health=yellow
health status index                               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   .monitoring-es-6-2017.12.09         EjmyMwfUR9GC7NpGE9J-Ew   1   1     444952         7688    303.3mb        303.3mb
yellow open   .watcher-history-3-2017.11.08       4yE6p3yrQ4WbRDr8Z6CHKw   1   1        660            0    617.2kb        617.2kb
yellow open   .watcher-history-3-2017.12.06       bnamUII8RHW0yqMtWS8xFg   1   1       7200            0      6.1mb          6.1mb
yellow open   .watcher-history-3-2017.12.05       GVDLk_p9T_6Mo2UNPw96PQ   1   1       7199            0      6.2mb          6.2mb
yellow open   .watcher-history-3-2017.10.26       bSAcokr1RsezlYVJTyzQQg   1   1       7200            0      5.8mb          5.8mb
yellow open   .watcher-history-3-2017.12.04       PkU3LewJR46a68dA8WUm1w   1   1       7200            0      6.1mb          6.1mb
yellow open   .watcher-history-3-2017.12.03       df6rtiv2SGKTWoP_G9S-8A   1   1       7200            0      6.1mb          6.1mb
yellow open   .watcher-history-3-2017.11.29       6HmPCZt8QUuMPeeKrZSa5w   1   1       7076            0      6.2mb          6.2mb
yellow open   .watcher-history-3-2017.11.28       leUNGN2uRyi76_fy_5zAOg   1   1       7200            0      6.2mb          6.2mb

こちらはindexの状態がyellowのものだけを表示しています。

終わりに

というわけで、 cat indices を例にして、cat APIs の使い方を見てきました。基本的には help でどのようなカラムが取得できるのか確認して、あとは表示項目絞ったり、ソートに使ったりしてください。

最後に重要なことを書いておきます。「公式のドキュメントをちゃんと読みましょう」と。

それでは!

Acroquest Technologyでは、キャリア採用を行っています。


  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

JUnit 4で消耗しているあなたに贈るJUnit 5入門

こんにちは、しんどーです。 気づいたら入社8ヶ月くらい経ってました。

さて、待望のJUnit 5のGA版が今年9月にリリースされました! この記事ではJUnit 5の概要と新機能の一部をご紹介したいと思います。 全部User Guideに書いてあるとか言わない

JUnit 5とは

JUnitとは、言わずと知れたJavaのテスティングフレームワークであり、 デファクトスタンダードの地位にあります。ですが現行のJUnit 4系の最初の メジャーバージョンリリースはすでに10年ほど前であり、保守性の低下が問題に なっていました。

そこでJUnitの刷新を目指すべく、JUnit 5プロジェクトが立ち上げられました。 Junit 5の特徴を簡単に述べると、

といったことが挙げられます。

JUnit 5のAPIは、JUnit 4のAPIとは互換性がありません。 したがって全く新しいフレームワークとして理解したほうがいいかもしれません。

で、JUnit 5って使えるの?

結論から言うと、一から再設計を行ったことによって、 JUnit 4のイマイチな部分が軒並み改善された印象です。

これまで設計上の制約からか、謎のお決まりごと(@Enclosedなクラスはstaticでないといけない等)が 多かったですが、JUnit 5はより自然で直感的なインターフェースデザインになっています。 お決まりコードも減りますので、多少のテストコード量の削減も期待できます。

ただし、すでに述べたようにJUnit 4とは互換性がありませんので、 既存のテストを書き換えるのはおすすめできません。 また新規のプロジェクトで使う場合も、IDE・ビルドツール・フレームワーク等の サポートにはご注意ください。 例えば、後述しますがJUnit 5.0系とmaven-surefire-pluginの2.20系の連携はバグがあり動作しません。

Springユーザであれば、Spring 5からJUnit 5のサポートがあるので、一緒に使い始めるのがいいかもしれません。 (逆にSpring 4でJUnit 5を使うのは大変かと思います。。)

JUnit 5のモジュール構成

JUnit 5は3つのサブプロジェクトから構成されています。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform

JUnitテストを実行するための基盤を提供します。Maven用、Gradle用のプラグイン等が用意されていますので、 各プロジェクトの環境に合わせたモジュールを選択します。

JUnit Jupiter

テストを記述するAPI(@Test etc...)とテストエンジンを提供します。 基本的に、「JUnit 5」と言った場合はJUnit JupiterのAPIを指すことが多いと思います。

JUnit Vintage

JUnit Platform上でJUnit 3 or 4を動かすためのテストエンジン等を提供します。 この記事では特に触れません。

Getting Started

さて、早速JUnit 5を動かしてみましょう。JUnit 4はたった一つのjarにまとまっていましたが、JUnit 5は複数の モジュールの組み合わせで動作します。(おかげで最初の1時間を無駄にしました。。)

シンプルなMavenプロジェクトを作成して、JUnit 5を動かします。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.github.rshindo</groupId>
  <artifactId>junit5-sample</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <junit.jupiter.version>5.0.2</junit.jupiter.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId>
      <version>1.0.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.19.1</version>
          <dependencies>
            <dependency>
              <groupId>org.junit.platform</groupId>
              <artifactId>junit-platform-surefire-provider</artifactId>
              <version>1.0.2</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

実はJUnit 5.0系 は現在 maven-surefire-plugin の最新版(2.20.x)に対応していません。(5.1でバグフィックスが入るようです)
https://github.com/junit-team/junit5/issues/809
ここでは 2.19.1 を利用しています。

pom.xmlを作成したら、次にテストクラスです。

import org.junit.jupiter.api.Test;
public class AppTest {

  @Test
  void testApp() {
    assertTrue(true);
  }
}

テストクラスを書いたら、最後にmvn testでテストを実行しましょう!

基本のテスト

JUnit 5のAPIJUnit 4とは互換性がありませんが、 基本的なアノテーションは パッケージと名前を変えるだけでほぼ同じように動きます。

JUnit4 JUnit5
@Test @Test
@Before @BeforeEach
@BeforeClass @BeforeAll
@After @AfterEach
@AfterClass @AfterAll
@Ignore @Disabled

前処理・後処理

前処理・後処理は @BeforeEach @BeforeAll @AfterEach @AfterAll を使います。

@BeforeAll
static void setUpAll() {
    System.out.println("before all tests");
}

@BeforeEach
void setUp() {
    System.out.println("before each test");
}

@Test
void test1() {
    System.out.println("test 1");
}

@Test
void test2() {
    System.out.println("test 2");
}

@AfterEach
void tearDown() {
    System.out.println("after each test");
}

@AfterAll
static void tearDownAll() {
    System.out.println("after all tests");
}

このときの出力は次のようになります。

before all tests
before each test
test 1
after each test
before each test
test 2
after each test
after all tests

テストケース名

テストの名前は @DisplayName で指定可能です。これまではテストメソッド名を日本語で書く人も多かったと思いますが、 @DisplayName であれば文字種の縛りもなく自由にテスト名を変えられます。

@Test
@DisplayName("1 + 1 = 2になるテスト")
void plusTest() {
    assertEquals(1 + 1, 2);
}

@Test
@DisplayName("😁") //絵文字も可能
void emojiTest() {
}

Eclipseで実行すると以下のように表示されます。

f:id:acro-engineer:20171207003848p:plain

注)mavenでレポート出力したら@DisplayNameが効いていませんでした。。

アサーション

ラムダ式に対応した、便利なアサートAPIが追加されています。 もちろん、assertTrueassertEquals もあります。

アサーションのグループ化

あるオブジェクトのプロパティを全て検証したいとき、これまでは以下のように書いていたと思います。

@Test
public void employeeTest() {
    Employee employee = employeeService.findById(1);

    assertEquals("Aoi", employee.getFirstName());
    assertEquals("Miyamori", employee.getLastName());
}

仮に1つ目のアサーションが失敗したとします。 その時点で例外が投げられ、その次のアサーションは実行されません。 1つ目のアサーションが通るように修正したら、 今度は2つ目のアサーションも失敗していることに気づいてまた修正に戻って・・・という経験をした方も多いと思います。

そんなときに役に立つのが、アサーションのグループ化です。

@Test
void employeeTest() {
    Employee employee = employeeService.findById(1);

    assertAll("employee",
        () -> assertEquals("Aoi", employee.getFirstName()),
        () -> assertEquals("Miyamori", employee.getLastName())
    );
}

assertAll の中にラムダ式で複数のアサーションを渡しています。 このように記述することで、片方のアサーションが失敗しても、 もう片方のアサーションを実行することができるのです!

例外のアサーション

JUnit 4での例外のアサーションはこうでした。

@Test(exception = NumberFormatException.class)
public void exceptionTest() {
    Long.valueOf(null); // throws NumberFormatException
    fail("Exception not thrown");
}

これが assertThrows によってこう変わります。

@Test
void succeedingTest() {
    NumberFormatException ex =
        assertThrows(NumberFormatException.class,
            () -> Long.valueOf(null));
    assertEquals(ex.getMessage(), "null");
}

assertThrows は、例外がthrowされなかった場合には アサーションが失敗となります。@Testには何も書きません。 assertThrows は例外オブジェクトを返しますので、 そこからメッセージの検証などもできます。

また、JUnit 4で例外が発生しなかったケースのために書いていた fail("Exception not thrown") という呪文も必要なくなるのです!

assertThat

様々なアサーションAPIが追加された一方、 assertThatJUnit 5では提供されていません。 JUnit 5の方針として、アサーションライブラリは開発者が好きなもの使ってね、というスタンスのようです。

assertThatを使いたい場合は、Hamcrestなどのサードパーティのライブラリを導入すればOKです!

構造化テスト

JUnit 4の @Enclosed の代わりに @Nested が導入されました。

public class EmployeeServiceTest {
    EmployeeService service;
    @BeforeEach
    void setUp() {
        this.service = new EmployeeService();
    }
    @Test
    @DisplayName("IDで検索する")
    void testFindById() {
        Employee employee = service.findById(1);
        assertAll("employee",
                () -> assertEquals("Midori", employee.getFirstName()),
                () -> assertEquals("Imai", employee.getLastName())
        );
    }

  //非static
    @Nested
    @DisplayName("名前で検索する")
    class FindByFirstNameStartingWithTest {
        @Test
        @DisplayName("firstNameが「E」から始まる")
        void startsWithE() {
      // アウタークラスのフィールドにアクセスできる
            List<Employee> employees = service.findByFirstNameStartingWith("E");
            assertEquals(employees.size(), 1);
            Employee employee = employees.get(0);
            assertAll("employee",
                    () -> assertEquals("Ema", employee.getFirstName()),
                    () -> assertEquals("Yasuhara", employee.getLastName())
            );
        }
    }
}

その違いは何と言っても、インナークラスが非staticになったことです! これによって、アウタークラスのプロパティを参照したり、前処理・後処理を共通化することができます。

まとめ

ここまでJUnit 5の新機能の一部を紹介してきました。 この他にも、パラメータ化テスト、タグ、Extensionなど 注目すべき機能がたくさんあります。 この機会にJUnit 5を是非触ってみてください! (できれば続き書きたいなあ。。)

Acroquest Technologyでは、キャリア採用を行っています。

  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 世の中に誇れるサービスを作りたいエンジニアwanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

あなたの知らないElasticsearch設定トップ5

こんにちは、 @ です。

こちらはElastic stack Advent Calendarの5日目の記事となります。
qiita.com

当社では、Elastic StackとX-Packの導入支援サービスを行っていますが、様々な事例に触れるにつれ、「この設定を知っていれば。。。」と思うことがあります。
Elastic Stackの設定が一覧化された資料は存在しないため、詳しく知るには公式ドキュメントをちゃんと読む必要があります。

自分の周囲に話を聞いてもあまり知られていなかったり、ネットで調べてもあまり載っていない設定もあったります。
そこで、重要な設定だけれど意外と知られていないものを中心に、5つほどご紹介します。
Elastic Stackのバージョンは5.6をベースに記述していますが、最近のバージョンであればあまり変わらないと思います。

1. シャード設計

インデックス設定の際に指定する"number_of_shards"。これは重要です。
REST APIで設定するこういうやつですね。

PUT twitter
{
    "settings" : {
        "index" : {
            "number_of_shards" : 3, 
            "number_of_replicas" : 2 
        }
    }
}

このREST APIについては、このページに詳しく書いてあります。
www.elastic.co


えっ、そんなの知っている?

そうですよね。
ただ、ここで言いたいのは単にシャード設定をしているだけでなく、
シャード設計を行った上で、シャード設定をしているか?
です。

Elastic社のブログに記載がありますが、シャード設計には経験則があり、
ヒープサイズが30GBの場合は1ノードあたりのシャード数は600-750個程度が限界との事です。
How many shards should I have in my Elasticsearch cluster? | Elastic

シャード数が多すぎるとクラスタに負荷がかかるため、シャード設計はとても大切です。

2. X-PackのDefault TLS/SSL Settings

elasticseachクラスタ内間の通信、elasticseachクラスタに対する通信には、SSL/TLSを使うことができます。
www.elastic.co


セキュリティは大切、、、というだけでなく、この設定は実は重要な設定です。
なぜかと言うと、Elastic Stack 6.0から
X-Pack Securityを利用する場合はSSL/TLSが必須となったからです。

以下のページにも記載があります。

Elasticsearch 6.0.0 GA released
www.elastic.co

これまで、メジャーアップグレードにはクラスタの再起動が必要でした。
クラスタを停止するとサービスに影響がある方も多いと思います。
そこで、5.6から6.0へのバージョンアップでは、ローリングアップグレード(1台ずつ停止してアップグレード)を行うことで、クラスタ全体の停止を避けることができるようになりました。

ただし、SSL/TLS化されていないクラスタの場合は、SSL/TLS化するためにクラスタの再起動が必要になります。
X-Pack Securityを使っている場合、SSL/TLS化が済んでないと、5.6から6.0にローリングアップグレードできないので注意しましょう。

3. Disk-based Shard Allocation

せっかくサイジングしても、Elasticsearchはデフォルト設定ではディスクを限界までは使いません
詳しくはこのページに記載がありますが、

www.elastic.co

ディスク使用率が85%を越えたノードには、新規シャードを割り当てません。
また、ディスク使用率が90%を越えたノードは、保持しているシャードを別のノードに再配置しようとします。

そのため、ディスクを限界まで利用するには、次のパラメータを設定する必要があります。

  • cluster.routing.allocation.disk.watermark.low
  • cluster.routing.allocation.disk.watermark.high

1TBディスクの85%だとは150GBしか空いていませんが、10TBディスクの85%だと1.5TBも空いてしまうので、利用しているディスクサイズによって設定したい値が変わってくると思います。

ただ、マージ前のセグメントサイズや、シャード再配置に必要な分はディスクに余裕が必要です。
また、耐障害性を考えると、1台停止しても運用できる余裕は必要ですので、ギリギリを攻めるのは避けた方が良いと思います。

4. プロセッサ数設定

Elasticsearchはインデクシング、検索など、処理毎にスレッドプールを持っています。
このスレッドプールは、Elasticsearch起動時にCPU数を元に自動で最適なサイズに設定されます。
www.elastic.co

ただし、プロセッサ数が32を越える場合は、プロセッサ数を手動で設定する必要があります

逆に、CPU数より小さく設定することでElasticsearchが利用するスレッドプールのサイズを小さくすることができます。
同じノードに他のプロセスが載っているケースで、利用すると良いと思います。

5. Merge scheduling

Elasticsearchで利用するディスクはSSDがオススメですが、デフォルトでSSD向けに自動でチューニングされるパラメータがあります。
Elasticsearchのデータは物理的には「セグメント」と呼ばれる単位で保存しており、ときおりセグメントをマージする処理が走ります。デフォルトの動作では、このマージ処理を行うスレッドの最大数が、SSD向けに自動でチューニングされます。もう少し言うと、CPU数によって、1-4のいずれかの値にチューニングされます。
www.elastic.co

正直言って、どの程度影響するのか、私も測定したことはありませんが、HDDを利用する場合は

index.merge.scheduler.max_thread_count

を1に設定するようマニュアルに記載されています。


以上、あなたの知らないElasticsearch設定トップ5でした。
知らないと思わぬ問題に陥ったりすることもあります。
十分条件ではありませんが、このあたりの設定を意識して、Elastic Stackを安全に運用しましょう。

それでは、また~

Acroquest Technologyでは、キャリア採用を行っています。


  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

Packetbeatをカスタマイズしてみた

こんにちは、イガラシです。

Acroquestでは社内勉強会がいくつかありますが、
その中の一つに、具体的に実践した内容を発表する会があります。
その会で先日、プロジェクトで取り組んだ「Packetbeatのカスタマイズ」を発表したのですが、
マニアックな内容にも関わらず、思いのほか好評だったので、ブログ記事として書きたいと思います。

Packetbeatのカスタマイズ、といっても公式に書いてある新しいプロトコルに対応する、というものではありません。
あるカプセル化されたパケットの中身を解析するため、Packetbeatが対応していないカプセル化プロトコルに対応しよう。
という少し(かなり)ニッチな対応を行いました。

今回対応したカプセル化プロトコルは、「NVGRE」になります。

パケットの解析処理

Packetbeatがキャプチャしたパケットを解析する処理は、gopacketによって行われています。
NVGREがGREの拡張だったので、gopacketが対応していたGREパケットの解析処理を修正して、NVGREパケットを解析できるようにしました。

NVGREの解析処理

gopacket/layers/gre.go

func (g *GRE) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
  g.ChecksumPresent = data[0]&0x80 != 0
  g.RoutingPresent = data[0]&0x40 != 0
  g.KeyPresent = data[0]&0x20 != 0
  g.SeqPresent = data[0]&0x10 != 0
 :
 :
  // ↓ここから修正したコード
  baseOffset = 4
  if g.ChecksumPresent {
    g.Checksum = binary.BigEndian.Uint16(data[baseOffset:baseOffset+2])
    g.Offset = binary.BigEndian.Uint16(data[baseOffset+2:baseOffset+4])
    baseOffset += 4
  }
  if g.KeyPresent {
    g.Key = binary.BigEndian.Uint32(data[baseOffset:baseOffset+4])
    baseOffset += 4
  }
 :
 :
}

元のソースでは、フラグによるオプションフィールドの有無に関わらず、フィールドの値を取得していました。
NVGREだと特定のフィールドしか設定されないため、フラグが有効な場合にフィールドの値を取得するように修正しました。

1.2.定数の定義
gopacket/layers/enums.go

const (
  EthernetTypeLLC                      EthernetType = 0
  EthernetTypeIPv4                     EthernetType = 0x0800
 :
  EthernetTypeEthernetCTP              EthernetType = 0x9000
  EthernetTypeTrasparentEtherBridge    EthernetType = 0x6558	// 追加した定数
)

NVGREで定義されている、ProtocolTypeの値(0x6558)を定数として追加します。


gopacket/layers/enums.go

func init() {
 :
  EthernetTypeMetadata[EthernetTypeLLC] = EnumMetadata{DecodeWith: gopacket.DecodeFunc(decodeLLC), Name: "LLC", LayerType: LayerTypeLLC}
  EthernetTypeMetadata[EthernetTypeIPv4] = EnumMetadata{DecodeWith: gopacket.DecodeFunc(decodeIPv4), Name: "IPv4", LayerType: LayerTypeIPv4}
 :
  EthernetTypeMetadata[EthernetTypeEAPOL] = EnumMetadata{DecodeWith: gopacket.DecodeFunc(decodeEAPOL), Name: "EAPOL", LayerType: LayerTypeEAPOL}
  // ↓追加した定義↓
  EthernetTypeMetadata[EthernetTypeTrasparentEtherBridge] = EnumMetadata{DecodeWith: gopacket.DecodeFunc(decodeEthernet), Name: "TransparentEthernetBridge", LayerType: LayerTypeEthernet}

ProtocolType別に、「Payload部分をどのプロトコルとして解釈するか?」という定義を追加します。
Packetbeatがパケットを解析するときに、この定義を使ってパケットの中身を解析していきます。


ここまでで、パケット解析ライブラリgopacketの拡張は完了です。

PacketbeatでNVGREに対応する

Packetbeatが、gopacketを使ってパケット解析を行う処理は、「decoder/decoder.go」に記述されています。
その中で、プロトコルスタックを順に解析する処理は、OnPacket()で行われています。

packetbeat/decoder/decoder.go

func (d *Decoder) OnPacket(data []byte, ci *gopacket.CaptureInfo) {

  for len(data) > 0 {
    err := current.DecodeFromBytes(data, d)
    if err != nil {
      logp.Info("packet decode failed with: %v", err)
      break
    }

    nextType := current.NextLayerType()
    data = current.LayerPayload()

    processed, err = d.process(&packet, currentType)
    if err != nil {
      logp.Info("Error processing packet: %v", err)
      break
    }
    if processed {
      break
    }

    // choose next decoding layer
    next, ok := d.decoders[nextType]
    if !ok {
      break
    }

    // jump to next layer
    current = next
    currentType = nextType
  }
}

OnPacketは、decodersで指定されたプロトコルの解析を行うようになっているので、decodersを初期化しているメソッドでGREを解析対象に含めるようにします。


packetbeat/decoder/decoder.go

func New(
  f *flows.Flows,
  datalink layers.LinkType,
  icmp4 icmp.ICMPv4Processor,
  icmp6 icmp.ICMPv6Processor,
  tcp tcp.Processor,
  udp udp.Processor,
) (*Decoder, error) {
 :
 :
  defaultLayerTypes := []gopacket.DecodingLayer{
    &d.sll,             // LinuxSLL
    &d.eth,             // Ethernet
    &d.lo,              // loopback on OS X
    &d.stD1Q,           // VLAN
    &d.stIP4, &d.stIP6, // IP
    &d.icmp4, &d.icmp6, // ICMP
    &d.tcp, &d.udp,     // TCP/UDP
  }
  d.AddLayers(defaultLayerTypes)
  d.AddLayer(&d.gre)	// 追加した行

「d.gre」は、Decoderのフィールド定義に追加しておきます。


ここまでの対応で、Packetbeatでカプセル化パケットNVGREを解析できるようになりました。
当初は、どうやってNVGREに対応すればよいか分からず、Packetbeat/gopacketのソースコードを読んでいました。
対応できてからは、以外と簡単に対応できるようになっているな、と思えるようになりました。

Packetbeatは便利ですし、このように改造もできるので、
是非みなさんも使ってみてください^^

Acroquest Technologyでは、キャリア採用を行っています。


  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

はてなブログをPython+Elasticsearch+Kibanaで可視化してみる

こんにちは。
普段はデータ分析している新人の佐々木です。

このブログに投稿したいと思いましたが、どんなトピックで書くか悩んでいました。
そこで、この記事ではどのようなトピックが今まで投稿されているのかを見てみたいと思います。

そこで、今回はこの「Taste of Tech Topics」自体をElasticsearch+Kibanaで可視化してみました。
完成画面の一部はこのようになりました。

f:id:acro-engineer:20170715173254p:plain

対象読者ですは次を想定しています。

  1. Elasticsearchを使い始めて、もっと使いこみたい人
  2. 日本語文書の可視化をしてみたい人

可視化のための手順は次の通りです

  1. Elasticsearchの設定
  2. データの解析、投入
  3. 可視化

1.Elasticsearchの設定

今回は次のmapping templateを作成し、適用しました。
適用方法はKibanaのDev Toolsを開き以下のリクエストを入力します。

PUT _template/blog
{
  "template": "blog.*",
  "settings": {
    "analysis": {
      "tokenizer": {
        "kuromoji": {
          "type": "kuromoji_tokenizer",
          "mode": "search"
        }
      },
      "analyzer": {
        "kuromoji-analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji",
          "filter": [
            "ja_stop",
            "kuromoji_part_of_speech",
            "lowercase"
          ],
          "char_filter": [
            "html_strip"
          ]
        }
      }
    }
  },
  "mappings": {
    "blog": {
      "properties": {
        "body": {
          "type": "text",
          "fielddata": true,
          "analyzer": "kuromoji-analyzer"
        }
      }
    }
  }
}

Elasticsearchではドキュメントの投入時にどのようにインデクシングするかを
tokenizerとfilterの二つの設定によって決められます。
それらと各フィールドの設定を定義するMappingを上で設定しています。
それぞれの設定について説明していきます。

tokenizer

tokenizerはテキストをどのように分割するかを決定する設定です。
kuromoji_tokenizerを使用しています。
modeをsearchに指定することによって、登録されている複合語を残しつつ、
分かち書きした単語が検索にヒットします。

例えば、「関西国際空港」の場合「関西国際空港」「関西」「国際」「空港」が検索にヒットします。

filter

filterはtokenizerで分割したテキストをどのようにElasticsearch内に登録するかを決定する設定です。
filterは、次の4種類使用しています。

  1. kuromoji_part_of_speech  助詞などを検索にヒットさせないために、特定の品詞を検索のときに除外します。
  2. ja_stop  kuromojiに登録されている日本語のストップワードを除外します。
  3. html_strip ブログのエクスポートデータはHTMLデータなので、htmlタグを除去します。
  4. lowercase 「Elasticsearch」と「elasticsearch」のような表記ゆれがあるので、アルファベットをすべて小文字に変換します。

Mapping

mapping定義ではbody(ブログ本文)を集計できる状態にしたかったので、
fielddataをtrueにしてあります。これでタグクラウドを単語ごとに表示できます。

2.データの解析、投入

使用したデータははてなブログをエクスポートしたテキストデータ(Movable Type)です。
エクスポート方法は以下の記事を参考にしました。
staff.hatenablog.com
形式は次のとおりです。

AUTHOR: acro-engineer
TITLE: X-Pack Machine Learningが正式リリースされました
BASENAME: 2017/07/08/150749
STATUS: Publish
ALLOW COMMENTS: 1
CONVERT BREAKS: 0
DATE: 07/08/2017 15:07:49
CATEGORY: Machine Learning
CATEGORY: Elasticsearch
CATEGORY: X-Pack
IMAGE: https://cdn-ak.f.st-hatena.com/images/fotolife/a/acro-engineer/20170707/20170707085241.png
-----
BODY:
===============本文===============
-----
COMMENT:
===============コメント===============
--------

本当はCOMMENTフィールド以外にもEXTENDED BODYフィールドなどがあるのですが、
今回のデータの中に含まれていませんでした。詳しいフォーマットは以下に記載されています
staff.hatenablog.com


これでは、これをElasticsearchに投入するために解析していきましょう。
今回はpythonを使いました。コードは次のようになりました。

import re
from elasticsearch import Elasticsearch
import datetime

columns = "AUTHOR|TITLE|BASENAME|STATUS|ALLOW COMMENTS|CONVERT BREAKS|DATE|CATEGORY|IMAGE"
blog_host = " http://acro-engineer.hatenablog.com/entry/"
template_pattern = re.compile("\<blockquote\>.+是非以下のページをご覧ください.+\<\/blockquote\>", flags=re.DOTALL)


class MovableParser(object):
    def __init__(self):
        """
        Elasticsearchと接続する
        """
        self.es = Elasticsearch(hosts=["localhost:9200"], http_auth=('elastic', 'changeme'))
        self.document = {}
        self.seq = ""

    def parse(self):
        """
        MovableTypeをパースする
        """
        #meta部分とbody部分を分割する
        elements = self.seq.split("-----\n")
        meta = elements[0]
        body = elements[1]
        #meta情報のパース
        meta_pattern = re.compile("({0}): (.*)".format(columns),flags=(re.MULTILINE | re.DOTALL))
        for metaline in meta.split("\n")[:-1]:
            matches = re.match(meta_pattern, metaline)
            if matches.group(1).lower() in self.document:
                print(matches)
                self.document[matches.group(1).lower()] += ",{0}".format(matches.group(2))
            else:
                self.document[matches.group(1).lower()] = matches.group(2)
        if "category" in self.document:
            self.document["category"] = self.document["category"].split(",")
            print(self.document["category"])

        body = re.sub("BODY:","",body)
        body = re.sub(template_pattern, "", body)
        self.document["body"] = body

        url = blog_host + self.document["basename"]
        self.document["source"] = url
        self.document["date"] = datetime.datetime.strptime(self.document["date"], "%m/%d/%Y %H:%M:%S")

        self.es.index(index="blog.{0}".format(datetime.date.today().strftime("%Y.%m.%d")), doc_type="blog",
                      body=self.document)
        print(self.document)
        self.document = {}

    def read_file(self, filename):
        """
        ファイルを読む。'--------'で区切ってparse()を呼び出す
        :param filename: データファイル名
        """
        with open(filename, encoding="utf-8") as f:
            for line in f:
                if line == "--------\n":
                    self.parse()
                    self.seq = ""
                else:
                    self.seq += line

if __name__ == "__main__":
    parser = MovableParser()
    parser.read_file("acro-engineer.hatenablog.com.export.txt")

データのパース

順序としては

  1. メタ情報とコンテント情報を分離
  2. メタ情報を正規表現でパースしてdocumentに追加
  3. コンテント情報からフッターを除去してdocumentに追加

となっています。

2のメタ情報のパースではあらかじめメタフィールド名を正規表現のパターンとして登録しています。
あとは1の際にCOMMENTフィールドは今回は解析対象に含まないためElasticsearchに入れていません。

データをElasticsearchに投入する

データの投入にはpythonのelasticsearchクライアントを使用しています。
pythonからElasticsearchへの投入、検索などが手軽に出来て便利なので重宝しています。
辞書型(dict)でドキュメントを定義すれば、Elasticsearch#indexで投入できます。

3.可視化

それでは、投入したデータをKibanaを使って可視化していきましょう。
まず、どのような単語がブログ本文で使われる頻度が多いのか見るために、
タグクラウドを作ってみます。

f:id:acro-engineer:20170717203244p:plain

重要そうな単語が短い単語と数字で埋もれてしまっていますね。
これではブログの内容が全然わかりません。
なのでExcludeに.|..|...|[0-9]+と指定して3文字以内の単語と
数字を取り除きます。すると

f:id:acro-engineer:20170717204057p:plain

このような画面になりました。技術用語などが出現するようになって
ブログの内容がわかるようなタグクラウドができてきましたね。

技術用語や挨拶の中に@cero_tのceroが混じっているのが面白いですね。
しかしまだ、「ちょっと」や「ください」などの単体では意味を持たない単語が気になります。

また、「アクセス」や「ファイル」など特徴的ではない単語も拾ってしまっています。
これらの単語は4文字がおおい印象です。そのため、まずは試しに、除外する単語を4文字以内にしてみます。

f:id:acro-engineer:20170717204804p:plain

すると、特徴的な言葉がタグクラウドに表されています。
Elasticsearchに関する記事が多いことが反映されていますね。
他にはAcroquestがどのような技術を扱っているか時系列で見てみたいので、
カテゴリ出現回数の時系列グラフも作ってみました。

f:id:acro-engineer:20170717205459p:plain

Elasticsearchが2015年あたりから増えた様子がわかりますね。
あとは詳細な回数を見るためのテーブルや
カテゴリの出現回数を分かりやすく表示するための棒グラフを
作成してダッシュボードにしてみました。

f:id:acro-engineer:20170717205952p:plain
f:id:acro-engineer:20170717210007p:plain

最後に

この記事ではブログのデータを取ってきてからKibanaで
可視化するところまで一通りやってみました。

ブログのデータから、トピックの傾向やキーワードが
ダッシュボードで見えるようになり、ブログの内容を
可視化することができました。

Elasticsearchの出現数が多く、現状のブログのカテゴリにも反映されていて、面白いですね!

ただ、固有の単語(x-pack,cero_tなど)が分割されてしまっています。
この点ですが、ユーザー辞書に固有の単語を登録して分割されないようにすれば、よくなると思います。

それではまたよろしくお願いします。

Acroquest Technologyでは、キャリア採用を行っています。

  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

Elastic Stack 6.0GAがリリースされました。

皆さんこんにちは
IBIS2017に参加した@です。

本日、Elastic Stack 6.0のGAがリリースされました。
そこで、今回の記事では、Elastic Stack 6.0GAの注目の機能を簡単に紹介します。

ちなみに、以前、Elastic Stack 6.0に関係する記事として、Elastic Stackの6.0のBeta版の紹介をしました。

acro-engineer.hatenablog.com

また、今回のElastic Stack 6.0GAについて公式からElastic Stack 6.0GAの紹介を次のサイトで行っています。
より詳細なElastic Stack 6.0 GAの変更点はこちらを確認してください。

Elastic Stack 6.0.0 GA is Released | Elastic

Elasticsearch

Elasticsearchの6.0GAでは主に検索やセキュリティ部分に改善が入りました。
Elasticsearchの検索性能が改善され、より検索が高速化しました。

また、5.6→6.xへのメジャーアップグレードに対してのRolling Upgrade機能が追加されています。
そのため、ダウンタイムなしでElastic Stackのアップグレードを実現できます。

更にセキュリティ機能で使われている標準のパスワード"changeme"がBeta版と同じく使われなくなっています。
標準のパスワードの代わりに自分自身で事前にパスワードを設定する必要があります。

Beta版ではありますが、Elastic Stack 6.0 Beta版紹介の記事で試しています。

Kibana

Kibanaの6.0GAで特に面白いと思っている機能・改善は次の4点です。

  1. Dashboardの自身のレイアウト、カラーの改善
  2. CSVのExport機能
  3. Full Screen機能のDashboard
  4. Watcher UI

Full Screen機能やDashboardの改善などのUI/UXの変化やKibana上から他のプロダクトを使いやすいようになりました。
まず、Metricbeatを使ってKIbanaで可視化しましたが、デザインが5.xより洗練されています。

f:id:acro-engineer:20171115102106j:plain

また、Elasticsearchに投入されているデータのCSVのExportについては従来まで
別のプラグインをインストールしなければ、CSV形式でデータを出力できませんでした。
Kibana6.0 GAからデフォルトでCSVのExport機能が入るようになっています。

そして、Watcherのクエリを簡単に作成できます。これまではJSONによるクエリを記述する必要がありました。
Kibana6.0からはWatcher UIを利用することでGUIでWatcherの操作・設定が可能となっています。

Elastic Stack 6.0 Beta版の記事で挑戦しましたがElasticsearchのクエリで記述する形式と比較して、非常に簡単でした。

Logstash

Logstashの6.0GAで特に注目しているのは、Pipeline Visualizerです。
Pileline Visualizerを使うことで、Logstashの処理の全体的な流れや各処理時間を
KibanaのMonitoringから確認できます。βの頃のPipeline Visualizerの写真ですが、次の通りです。

f:id:acro-engineer:20171115095623p:plain

Beta版のPipeline Visualizerを試しに利用しましたが、デバッグの際に非常に便利だと感じています。

Beats

Beatsの6.0GAで特に注目している機能は2つあります。

  1. Filebeatを利用したKubernetesのメトリック取得
  2. Audit Beatのリリース

まず、Filebeatを利用したKubernetesのメトリック取得が可能となりました。
KubernetesはDockerのオーケストレーションを行うソフトウェアです。そのKubernetesから情報をBeatsで簡単に取得し、Elastic Stackで分析できます。
そのため、Kubernetesの情報をBeatsでどこまでとれるのか、個人的には気になるところです。

そして、Auditbeatはファイルの改竄検知ができるBeatで、作成ファイル数や修正ファイル数を取得できます。
このBeatの利用により、期待しないファイルの編集が行われたかどうかをElasticsearchで監視することができます。

こちらは以前、Elastic Stack 6.0 Beta版紹介の記事で使用しました。

最後に

待ちに待ったElastic Stack 6.0GAがリリースされました。
これから使い倒していきます!

Acroquest Technologyでは、キャリア採用を行っています。

  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com