中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

自然言語解析 in MONMO(後編)

一連の自然言語処理MONMOちゃん上で実現する試みの第3弾
前回はベクタライズまで行った。

今回は、形態素解析結果から、そのドキュメントの特徴を表す『ベクトル』を算出する、ベクタライズを行う。

k-means

非階層型のクラスタリング手法。
シンプルで軽量。
ある程度の分散処理も可能。

概要

初期値として、仮重心を複数与え、各ベクトルは一番近い重心に所属するものとし、それらの平均値を新たな重心とする。
これを繰り返す事で、仮重心は移動して行き、最終的に動かなくなる。それが本当の重心と言える。

但し、初期値として与える仮重心の個数や値ににより、結果が大きく変わる。



canopy

非常にシンプルで荒っぽいクラスタリング手法
分散処理は難しい。

k-meansの最大の問題である初期値の算出によく利用される。

概要

いわゆる碁石拾いである。
ベクトルをランダムに1つ選び、それを仮重心とする。さらに仮重心から半径(T2)以内のベクトルを削除する。
これをベクトルが無くなるまで繰り返す。
仮重心から半径(T1)以内のベクトルの平均値を算出し、それを重心とする。


monmo-NLProcessing/clusterize

clusterize
https://github.com/monmo/monmo-NLProcessing/blob/master/clusterize/README
流れ
  1. ベクトルをcanopyクラスタリングを掛ける。
  2. canopy結果を利用してkmeansクラスタリングを掛ける。

クラスタライズ準備

前々回の手順で完了している。
ディレクトリを移動するだけ

cd monmo-NLProcessing/clusterize
サンプルデータをクラスタリングする

  1.サンプルデータを投入(2500件のx,y座標データ)

mongoimport -d test -c samplevector --file ../sample/samplevectors2.json

  2.Canopy

./canopy.sh -s test.samplevector -f loc -2 20 -1 22

  3.仮重心を確認

./viewcluster.sh -s test.canopy.samplevector.cluster
====== 0 ( 608 ) ======
{ "id" : "y", "s" : -49.85003289473686 }
{ "id" : "x", "s" : -50.286003289473705 }
====== 1 ( 572 ) ======
{ "id" : "y", "s" : 51.6208916083916 }
{ "id" : "x", "s" : -48.94491258741262 }
====== 4 ( 425 ) ======
{ "id" : "y", "s" : 51.91148235294116 }
{ "id" : "x", "s" : 44.45475294117645 }
====== 6 ( 540 ) ======
{ "id" : "x", "s" : 48.769388888888855 }
{ "id" : "y", "s" : -51.55907407407403 }

  4.Kmeans

./kmeans.sh -s test.samplevector -f loc -i test.canopy.samplevector.cluster

  5.クラスター確認

./viewcluster.sh -s test.kmeans.samplevector.fin.cluster
====== 0 ( 626 ) ======
{ "id" : "y", "s" : -49.49702875399362 }
{ "id" : "x", "s" : -50.793690095846664 }
====== 1 ( 626 ) ======
{ "id" : "y", "s" : 49.92397763578274 }
{ "id" : "x", "s" : -49.16311501597446 }
====== 4 ( 624 ) ======
{ "id" : "y", "s" : 50.24775641025642 }
{ "id" : "x", "s" : 49.465288461538435 }
====== 6 ( 624 ) ======
{ "id" : "x", "s" : 50.32350961538466 }
{ "id" : "y", "s" : -49.74504807692312 }

  付属のclusterviewerで、視覚的に確認する事もできる。
  

 ※mongodの起動オプションにjsonp関連の設定が必要

rest=true
jsonp=true

  サンプル

クラスタリング自然言語処理:関連ドキュメントを算出)

前回行ったベクタライズ結果(ベクトル)をクラスタリングする。

  1.Canopy

./canopy.sh -s test.vector.tfidf.token.sampledoc

  2.仮重心を確認

./viewcluster.sh -s test.canopy.vector.tfidf.token.sampledoc.cluster
====== 27 ( 5 ) ======
0.6991590463302894       : 紙
0.26116572843727387      : 板紙
0.25448682647478826      : トイレットペーパー
0.20883262479957973      : 製紙
0.19898074919588601      : 布
0.14420424256537573      : 芯
0.14021816226652337      : パルプ
0.1301327196970886       : 王子
0.09387761477214077      : 業
0.09332310672575826      : 原紙
====== 28 ( 2 ) ======
0.5029816857228637       : Firebird
0.4098154219626097       : PostgreSQL
0.18170394267364035      : テーブル
0.17180653606418556      : インデックス
0.15644102504605537      : SQL
0.1476751483723454       : Best
0.13266129033454707      : バージョン
0.12598011447679408      : 閲覧
0.11908756100369494      : データベース
0.11393026693281044      : 2009年
====== 3 ( 12 ) ======
0.4408980420027375       : Apache
0.2540765415131882       : Log
0.163021939539478        : Axis
0.14548038477551709      : Net
0.13875718139417623      : Hadoop
0.130368792455702        : Maven
0.1267163309098733       : mod
0.12508219419623304      : Erlang
0.11450975136013103      : Ant
0.11102670657856481      : Tomcat
  :
  :
  :
====== 8 ( 1 ) ======
0.8057625546526619       : R
0.33396552734146134      : 関数
0.1665807620029282       : 犬
0.15287848869715082      : 解析
0.14516184139211946      : 統計
0.12807083125784488      : パッケージ
0.11105384133528545      : リスト
0.07643924434857541      : f
0.07565769634531941      : 行列
0.0615125651459316       : PDF
====== 9 ( 2 ) ======
0.5030288277932503       : 大臣
0.3782776791666038       : 政務
0.3634129449830944       : 内閣
0.2881511119264911       : 官
0.2403024083749958       : 府
0.1912386335984169       : 条
0.1690473739354759       : 委員
0.14240962826254328      : 法律
0.1226773027532713       : 庁
0.1180222505621871       : 担当


  3.Kmeans

./kmeans.sh -s test.vector.tfidf.token.sampledoc -i test.canopy.vector.tfidf.token.sampledoc.cluster

  4.クラスター確認

./viewcluster.sh -s test.kmeans.vector.tfidf.token.sampledoc.fin.cluster
====== 27 ( 13 ) ======
0.6710317847485132       : 紙
0.21439418158242424      : 印刷
0.1768292696988328       : 折り紙
0.16236873170000543      : 新聞
0.16236198982268538      : 段ボール
0.15825358425477087      : 板紙
0.14641634234324327      : 製紙
0.14031243070531707      : トイレットペーパー
0.11932858638000869      : ペーパーナイフ
0.11669516517623972      : 芯
  :
  :
  :

   詳細:

./viewcluster.sh -s test.kmeans.vector.tfidf.token.sampledoc.fin.cluster -V
====== 0 ( 7 ) ======
0.4405965585238873       : 宇宙
0.3487744336004021       : 探査
0.33117813139430435      : 月面
0.32402389175263796      : 計画
0.24126460324385507      : 月
0.23420944668374247      : アポロ
0.19723888046346477      : 着陸
0.193779193883831        : 号
0.13895807931926288      : 飛行
0.12638293574816634      : 有人
_id:(51e64d60c507ed1f43d213c4) : 宇宙開発出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索宇宙開発(うちゅうかいはつ、英:spacedevelopment)は、宇宙空
_id:(51e64d60c507ed1f43d213c7) : 月面着陸出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索月面でポーズをとるエドウィン・オルドリン。撮影したニール・アームストロングが
_id:(51e64d60c507ed1f43d213e4) : アポロ計画出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索アポロ計画(アポロけいかく、Apolloprogram)とは、アメリカ航空
_id:(51e64d60c507ed1f43d213f7) : 月面車出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索月面車(げつめんしゃ)とは、月面上を走行するために造られた自動車のこと。目次1
_id:(51e64d60c507ed1f43d213e6) : 宇宙開発競争出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索宇宙開発競争(うちゅうかいはつきょうそう、SpaceRace、宇宙開発レ
_id:(51e64d60c507ed1f43d213f9) : 月探査出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索月面に近づくアポロ12号の月着陸船物理的な月探査(Explorationoft
_id:(51e64d60c507ed1f43d213fd) : 宇宙探査機出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索この記事は検証可能な参考文献や出典が全く示されていないか、不十分です。出典
  :
  :
  :
解説

2.の出力は1.Canopyの結果。
フォーマットは以下の通り。

====== <クラスターID> ( <所属ドキュメント数> ) ======
<上位キーワードの重み> : キーワード
  :

4.のKmeans後は少し重みが変わっており、所属ドキュメント数も変化し、重心が少し移動した事が解る。

まとめ

ここまでで関連ドキュメントを算出するところまで出来た。

まだ差分計算などの課題は有りつつも、一通りの自然言語処理が出来た。

今後ともMONMOちゃんをよろしく!!

自然言語解析 in MONMO(中編)

一連の自然言語処理MONMOちゃん上で実現する試みの第2弾
前回は形態素解析まで行った。

今回は、形態素解析結果から、そのドキュメントの特徴を表す『ベクトル』を算出する、ベクタライズを行う。

TF-IDF

自然言語処理における代表的なベクタライズ手法。


考え方
  1. ドキュメント中、何回も出現する単語はそのドキュメントを表す重要な単語である。
  2. 多くのドキュメント中に出現する単語は普遍的な単語なので重要ではない。

シンプルだ。


TF-IDFの要素
N
総ドキュメント数
TF[a]
ある単語(a)がその1ドキュメント中に現れた回数
DF[a]
ある単語が現れたドキュメント数
IDF[a]
log( N / DF[a] )
TF-IDF[a]
TF[a] x IDF[a]
TF-IDFの例

私はそれを読みたい。
私はそれを書きたい。

"私はそれを読みたい。"={
  "私"   : 0,
  "は"   : 0,
  "それ" : 0,
 "を"   : 0,
  "読み" : 0.3,
  "たい" : 0
}
"私はそれを書きたい。"={
  "私"   : 0,
  "は"   : 0,
  "それ" : 0,
 "を"   : 0,
  "書き" : 0.3,
  "たい" : 0
}
  • N=2
  • 『私』『は』『それ』『を』『たい』のIDFはlog(2/2) = log(1) = 0; よってTF-IDFも0となる。
  • 『読み』『書き』のIDFはlog(2/1) = log(2) = 約0.3;
  • 『読み』『書き』のTFは、それぞれ1

これはあくまで例。
実際はDF=1 の単語は他のドキュメントと結び付けようが無く、無視して良い。
(処理量を減らすために積極的に削るべき)

そんな訳で

今回も、これをMONMOちゃんのMAPジョブを使って大量(?)のドキュメントと品詞を並列に処理する。


monmo-NLProcessing/vectorize

vectorize
https://github.com/monmo/monmo-NLProcessing/blob/master/vectorize/README
処理順
  1. tokenize結果からTFを算出
  2. TFからDFを算出
  3. DFからIDFを算出
  4. TF & IDF からTIF-IDFを算出

MongoDBの特性を考慮すると、この順が一番効率が良いだろう。


ベクタライズ準備

前回の手順で完了している。
ディレクトリを移動するだけ

cd monmo-NLProcessing/vectorize
ベクタライズ(簡易版)
./vectorize.sh -s test.token.sampledoc
文書検索
TF結果を使うと高度な検索が出来る。
./fulltext_search.sh -s test.vector.tf.token.sampledoc -w 'はさみや糊など' -V
= META =
{
        "dic" : "analysis.dictionary",
        "doc" : "test.sampledoc",
        "doc_field" : "body",
        "docs" : 73,
        "normalize" : true,
        "tf" : "test.vector.tf.token.sampledoc",
        "token" : "test.token.sampledoc",
        "type" : "TF"
}
= DIC =
5212ed32b399b667b8567608 => はさみ
5212ed33b399b667b85685b3 => や
5212ed55b399b667b858df1f => 糊
5212ed32b399b667b85671f3 => など
= QUERY =
{
        "value.w" : {
                "$all" : [
                        "5212ed32b399b667b8567608",
                        "5212ed33b399b667b85685b3",
                        "5212ed55b399b667b858df1f",
                        "5212ed32b399b667b85671f3"
                ]
        }
}
= DOCS =
[ ObjectId("51e64d60c507ed1f43d21400") ]
= VERBOSE =
 * 51e64d60c507ed1f43d21400 : 折り紙出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索この項目では、紙を折る遊びについて記述しています。"折紙"、"折り紙"の他の用
ベクタライズ(正規手順)

  1.TF

./tf.sh    -s test.token.sampledoc            -o test.vector.tf.token.sampledoc

  2.DF

./df.sh    -s test.vector.tf.token.sampledoc  -o test.vector.df.token.sampledoc

  3.IDF ※要チューニング

./idf.sh   -s test.vector.df.token.sampledoc  -o test.vector.idf.token.sampledoc

  4.TF-IDF

./tfidf.sh -s test.vector.idf.token.sampledoc -o test.vector.tfidf.token.sampledoc
IDFチューニング

このフェーズで色々なチューニングをする。

  1.DFとIDFを確認する。

./view_df.sh -s test.vector.df.token.sampledoc
./view_df.sh -s test.vector.idf.token.sampledoc

  2.上を確認しながら、limit,threshold,verb-onlyの値を調整する。

limit
(DF / 総ドキュメント数)の最大値
threshold
DFの最小値
verb-only
名詞だけ抽出
切り捨て過ぎの場合はlimit値を下げる
./idf.sh --limit 0.3   -s test.vector.df.token.sampledoc  -o test.vector.idf.token.sampledoc
もっと切り捨てたい場合はlimit値を上げる
./idf.sh --limit 0.5   -s test.vector.df.token.sampledoc  -o test.vector.idf.token.sampledoc
名詞だけを評価する(大抵の場合、条件を緩くした方が良い)
./idf.sh --limit 0.3 --verb-only -s test.vector.df.token.sampledoc  -o test.vector.idf.token.sampledoc

  3.結果が良くなるまで繰り返す

  4.TF-IDFを再産出

./tfidf.sh -s test.vector.idf.token.sampledoc -o test.vector.tfidf.token.sampledoc

まとめ

これで、ドキュメントをベクトル化する所まで出来たMONMOちゃん
ベクトルはドキュメントの特徴を数学的にパラメータ化したもので、後は数学的手法によって色々な使い方が出来る。

次回はベクトルのクラスタリングを行う予定。
ドキュメントの関連度や、グループ化などが出来る!!

修正

TF=1 => DF=1 の単語は・・・

ご指摘ありがとうございます!!

自然言語解析 in MONMO(前編)

前回MONMOちゃんの紹介の続き。

今回は(日本語)自然言語解析の第一歩であるトークンナイズ(tokenize)を行う。


形態素解析

日本語の解析で一般的に使われるtokenize手法で、辞書を使った文脈解析。

メジャーな形態素解析ライブラリは以下の様なものがある。

mecab
https://code.google.com/p/mecab/
lucene-gosen
http://code.google.com/p/lucene-gosen/
ちょっと説明

ラテン語圏だと大抵の文章はスペース区切りで表記される。

I want to read it.

これをプログラムで扱う場合は、単純にスペースの部分で切れば良い。

  • I
  • want
  • to
  • read
  • it
  • .

しかし日本語ではそうは行かない。
どの品詞(+活用形)の後に何が接続できるのか?
などを判断しながら以下の様に分割する。

私はそれを読みたい。

  • 私 (名詞)
  • は (助詞)
  • それ(名詞)
  • を (助詞)
  • 読み(動詞-連用形)
  • たい(助動詞)

実は英語でも

Iwanttoreadit.

と書いても、大抵の人は理解できる。
この時、頭の中で行っている作業が、形態素解析という訳だ!


そんな訳で

今回は、これをMONMOちゃんのMAPジョブを使って大量(?)のドキュメントを並列に処理する。



monmo-NLProcessing/tokenize

tokenize
https://github.com/monmo/monmo-NLProcessing/blob/master/tokenize/README

形態素解析の処理はそれなりに複雑であるが、主に以下の理由からMONMO用に形態素解析エンジンを新しく作った。
 1. 既存のライブラリは辞書を専用コンパイルする必要があったりして、辞書の取り回しが面倒。
    MongoDBで処理する以上、辞書もMongoDBにあるべきだ。
 2. 同様にインプット、アウトプットもMongoDBに直接行った方がいい。
 3. 既存ライブラリはJSではないので、MONMOの仕組みを使ってJOB実装を叩きつける設計には向かない。
 4. 解析精度に納得がいかなかった時にチューニングする際JSの方が楽だ。(V8速いし)


形態素解析環境、構築手順

 0. 前回の手順でMONMOを構築
  https://github.com/monmo/monmo/blob/master/README
 1. プロジェクトをgit clone

git clone git@github.com:monmo/monmo-NLProcessing.git
cd monmo-NLProcessing/tokenize

 2. IPA辞書をDL

pushd ./data
wget http://iij.dl.sourceforge.jp/ipadic/24435/ipadic-2.7.0.tar.gz
tar xzf ipadic-2.7.0.tar.gz
popd

 3.辞書をビルド(2,3分かかる)

IPA辞書を解析し、修正とインデックス用のデータを補完して、MongoDBへ入れる。
./gendic.sh -i data/ipadic-2.7.0

 4.動作確認(形態素解析結果が出力される)

./test.jp.sh -i "私はそれを読みたい。"
1 : 私
2 : は
3 : それ
4 : を
5 : 読み
6 : たい

お手軽でしょ?


並列解析

さて、いよいよ、多数のドキュメントを扱っていく。

 1.テストデータをMongoDBに投入(73件:wikipediaから抽出)

mongoimport --drop -d test -c sampledoc --file ../sample/sampledocs.json

 2.複数ドキュメントを一気に解析(MAPジョブ)

./tokenizer.jp.sh -s test.sampledoc -f body -o test.token.sampledoc

 3.結果コレクションを確認

mongo test <<<'db.token.sampledoc.find().sort({docid:1,idx:1})'

 4.(結果を使って)キーワード検索
   『ペーパー』を含むドキュメント:

『ペーパー』を含むドキュメント:
./keyword_search.sh -s test.token.sampledoc -w 'ペーパー' -V
= META =
{
        "dic" : "analysis.dictionary",
        "doc" : "test.sampledoc",
        "doc_field" : "body",
        "token" : "test.s",
        "type" : "TOKEN"
}
= QUERY =
{ "w" : "ペーパー" }
= DOCS =
[
        {
                "d" : "51e64d60c507ed1f43d213d4",
                "n" : 19,
                "w" : {
                        "ペーパー" : 19
                }
        }
]
= VERBOSE =
 * 51e64d60c507ed1f43d213d4 : 電子ペーパー出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索iLiad電子ペーパー(でんしペーパー)とは、紙の長所とされる視認性や携
『ペーパー』から始まる単語を含むドキュメント:
./keyword_search.sh -s test.token.sampledoc -w 'ペーパー' -F -V
※1:下部に添付


2.の段階でこの様に複数のWORKERがMAPジョブを同時に処理し
サンプルドキュメント達を手分けして解析している。


熟語解析(β版)

ついでに形態素解析結果からの、(C-VALUE)熟語解析も作ってみた。
基本はTFをベースに処理するが、色々試した結果DFを使った方が良さそうなので、そちらを採用。

 1.熟語解析

./phrase.sh -s test.token.sampledoc

 2.熟語解析結果

cvフィールドが大きい程、熟語の確率が高い:
mongo test <<<'db.phrase.token.sampledoc.find({cv:{$gt:0}}).sort({cv:-1})'

 3.結果をどう使えば良いのか?を検討中・・・


まとめ

こんな感じで簡単に形態素解析が出来る様になったMONMOちゃん

次回はより複雑な検索に対応する為のベクタライズ(ベクトル化)を予定!!



添付

※1
= META =
{
        "dic" : "analysis.dictionary",
        "doc" : "test.sampledoc",
        "doc_field" : "body",
        "token" : "test.s",
        "type" : "TOKEN"
}
= QUERY =
{ "w" : /^ペーパー/ }
= DOCS =
[
        {
                "d" : "51e64d60c507ed1f43d213d4",
                "n" : 20,
                "w" : {
                        "ペーパー" : 19,
                        "ペーパーコンソーシアム" : 1
                }
        },
        {
                "d" : "51e64d60c507ed1f43d213dc",
                "n" : 13,
                "w" : {
                        "ペーパーナイフ" : 13
                }
        },
        {
                "d" : "51e64d60c507ed1f43d213d9",
                "n" : 7,
                "w" : {
                        "ペーパークラフト" : 7
                }
        },
        {
                "d" : "51e64d60c507ed1f43d213f5",
                "n" : 6,
                "w" : {
                        "ペーパークラフトペーパー" : 1,
                        "ペーパーナイフ" : 1,
                        "ペーパーバルカナイズドファイバーシュレッダー" : 1,
                        "ペーパーレス" : 2,
                        "ペーパーロード" : 1
                }
        },
        {
                "d" : "51e64d60c507ed1f43d213e4",
                "n" : 1,
                "w" : {
                        "ペーパークラフト" : 1
                }
        },
        {
                "d" : "51e64d60c507ed1f43d21400",
                "n" : 1,
                "w" : {
                        "ペーパークラフト" : 1
                }
        },
        {
                "d" : "51e64d60c507ed1f43d213e6",
                "n" : 1,
                "w" : {
                        "ペーパークリップ" : 1
                }
        },
        {
                "d" : "51e64d60c507ed1f43d213dd",
                "n" : 1,
                "w" : {
                        "ペーパープロダクツ" : 1
                }
        }
]
= VERBOSE =
 * 51e64d60c507ed1f43d213d4 : 電子ペーパー出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索iLiad電子ペーパー(でんしペーパー)とは、紙の長所とされる視認性や携
 * 51e64d60c507ed1f43d213d9 : ペーパークラフト出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索ペーパークラフトとは、紙を素材としてつくる模型で、「カードモデル」と
 * 51e64d60c507ed1f43d213dc : ペーパーナイフ出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索この記事は検証可能な参考文献や出典が全く示されていないか、不十分です。
 * 51e64d60c507ed1f43d213dd : 日本の企業一覧(パルプ・紙)出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索日本の企業一覧(パルプ・紙)(にほんのきぎょういちらんパ
 * 51e64d60c507ed1f43d213e4 : アポロ計画出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索アポロ計画(アポロけいかく、Apolloprogram)とは、アメリカ航空
 * 51e64d60c507ed1f43d213e6 : 宇宙開発競争出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索宇宙開発競争(うちゅうかいはつきょうそう、SpaceRace、宇宙開発レ
 * 51e64d60c507ed1f43d213f5 : 紙出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索紙(かみ)とは、植物などの繊維を絡ませながら薄く平(たいら)に成形したもの。日本工
 * 51e64d60c507ed1f43d21400 : 折り紙出典:フリー百科事典『ウィキペディア(Wikipedia)』移動:案内、検索この項目では、紙を折る遊びについて記述しています。"折紙"、"折り紙"の他の用

MongoDB製JOB Queue

お盆が暇だったので
MongoDB製Job queue を作った。
名前はMONMOちゃん

javascriptで手軽に使いたい部分があって個人用で考えていたが
結構マトモなモノが出来上がったので公開する事にする。

またMONMOちゃんを使って、自然言語処理も一式書いてみたが
こちらは次回紹介する。

注意

Javascriptではない。
MongoDB製だ!


繰り返し言おう。

MongoDBは環境である!!

About Monmoちゃん

github
https://github.com/monmo/monmo
概要
  • 全ての処理はMongoDB(mongod) 及び Mongo shell(mongo)上で動作する。
  • JobはJavascriptで記述する。
  • MongoDBへJob投入(制御データと実装)すると、予めどこかで起動したWorkerが処理する。
  • Job投入側にはスクリプトを用意した。
  • 現在のJobの種類
    • 通常Job
    • Map Job
    • MapReduceは実装作業中・・(emitを効率化できてなくて困ってる。。)
なぜ必要?

MongoDBにも以下の様なロジックを走らせる仕組みはある。

しかし、これらはサーバサイド(mongod)で走る。
OraclePL/SQLの様なモノだ。DBでアプリ処理を行えば割り食ってDBの性能が落ちる。
下手するとDB自体が落ちる!

なんの為に分散DB使ってるんだ!?
と・・・

そこでMongoDBクライアントであるMongo shell (mongo)を利用する事にした。
Mongo shellはMongoDBをオペレーションする時に不可欠なシェルだが、只のreadline程度のものじゃない。
V8搭載の高性能なJavascript実行環境だ!

MONMOちゃんの構成はこんな感じだ。

JOB


MAP


使い方

準備も利用も超簡単!

== 準備 ==
1. MongoDBを立てる。
2. MONMOちゃん一式をgit cloneする。(上記github
3. MONMOの設定ファイルにMongoDBのパスを指定
4. MONMOワーカーを立てる。

# ワーカーを4プロセス立てる
$ ./worker.sh -J 4

== JOB投入 ==
5. Job実装ファイルを作る

== samplejob.js ==
function main(jobctl,options) {
 jobctl.put( function(){
  return new Job({
   // Job投入側の処理
   //(
   //  サンプルなのでコレクションにデータを入れている
   //  本来はJOBの制御情報を作る処理になる
   //  )
   create_job : function(){
    this.src.drop();
    this.src.save({_id: 1,n:1});
    this.src.save({_id: 2,n:1});
    this.src.save({_id: 3,n:1});
    this.src.save({_id: 4,n:1});
    this.src.save({_id: 5,n:1});
    this.src.save({_id: 6,n:2});
    this.src.save({_id: 7,n:2});
    this.src.save({_id: 8,n:2});
    this.src.save({_id: 9,n:2});
    this.src.save({_id:10,n:2});
    return { ok:1 };
   },
   // Worker側で実行される処理(只のSUM)
   run : function(){
    var data = {i:0,n:0};
    this.src.find().forEach(function(doc){
     data.i += doc._id;
     data.n += doc.n;
    });
    this.dst.save(data);
    return {
     ok:1,
     msg: 'success',
     data: data
    };
   },
  });
 },options);
}

6. Jobを投入

# "test" DB "TEST"コレクションを入力側、"test" DB "RESULT"コレクションを出力側として指定
# 
$ ./jobctl.sh -s test.TEST -o test.RESULT  -f samplejob.js

7. 結果

- Load : ./sample/jobs/samplejob.js
{
   "_id" : ObjectId("5218092e241076268cbc9b71"),
   "args" : {
   },
   "dst" : "test.RESULT",
   "name" : "test.RESULT.job",
   "src" : "test.TEST",
   "st" : 1377306937204,
   "timeout" : 600000,
   "tm" : ISODate("2013-08-24T01:15:26.800Z"),
   "end" : ISODate("2013-08-24T01:15:27.209Z"),
   "ok" : 1,
   "msg" : "success",
   "data" : {
    "i" : 55,
    "n" : 15
   }
}

熟語解析が難しい。。。(もはや走り書きメモ)

修正: x: unigram => o: bigram
 勢いで書くとこうだよ・・・

形態素解析でTokenize後にN-gramで熟語解析しようとしたが、ちと困った。。

 日本 維新 の 会

これをbigramすると
 日本維新
 維新の
 の会

となって、これを使ってもう一回Tokenizeすると

 日本維新 の会

更にbigramしてやっと完成する。

 日本維新の会

ここから、もう一回Tokenizeすると望む結果になるが、、

3回もTokenizeしたら日が暮れるじゃ済まないよ・・・

追記

とりあえず、一気に4-gram, 3-gram , 2-gram とかけてみる事にした。
辞書に無駄な登録をしない様に、登録前に熟語候補のDFを評価してフィルタ。

更に、『日本維新の会』に対して
 日本維新の
 維新の
 の会
みたいなのは、DFも掻い潜って来るのだが、、これはどうすれば良いんだろ・・・

先頭一致じゃない奴は評価が難しいな・・・

さらに追記

DF(日本維新の) >= DF(日本維新の会)

になってしまうので、

 本当のDF(日本維新の) = DF(日本維新の) - DF(日本維新の会)

と評価する事は出来るが、、若干重い。。
 (4-gram , 3-gram , 2-gramとやっているのでかなりの量がある。。)

更に、同じことは

  DF(維新の会) >= DF(日本維新の会)

でも言えるのだけど、こちらは前方マッチに掛けられないので、苦しい・・・

更に追記

FBで色々教えて頂きながら、組上げる事ができた。

色々メモを残しておいて、後でまとめよう。。

どうやらn-gramの結果から熟語を得る場合、C-VALUEを使う方法があるらしい。。

C-VALUE

DNA解析などで用いられている、全体と部分を評価する手法らしい。

Cが何なのか解らなかったのでwikipediaを引いてみたが・・・
I am afraid the letter C stood for nothing more glamorous than 'constant'

という訳で、たいした意味は無いっぽいw

C-VALUE(W) = ( n - 1 ) x ( tf(W) - t(W)/c(W) )

tf(W)
Wという単語の頻出度
t
Wという単語を含む単語の頻出度
c
Wという単語を含む単語の数(種類数)

式を分解して考えるとこうゆー事らしい。。

(n-1)
長い単語ほど価値が高い
tf(W)
頻出するものは価値が高い
- t(W) / c(W)
その単語を含むより長い単語がある場合は、価値が下がる。
しかしその様な単語が沢山ある場合は、下落率は低くなる。
(その単語にも何らかの意味があるはずだ!)

つまり
日本維新の会】 が5回現れて【維新の会】 が5回現れたとしたら、【維新の会】は完全に【日本維新の会】の一部であるので、価値はC-VALUEはゼロ。

そうではなく
日本維新の会】 が4回現れて、【大阪維新の会】が1回現れて【維新の会】が5回現れたとしたら、【維新の会】は単独で現れた訳では無いが、若干の意味があるはずだ。

実際は、、

tfをベースとすると、単独や少数のドキュメントの誤用法やタイポ、単に連呼してるだけ、、の様なものが上位に食い込みやすい。
どうもdfベースとした方が、好ましい結果がでるように感じた。

また、(n-1)の項は熟語解析には強すぎる。
橋下徹代表】が【橋下徹】よりずっと上に来るのはどうなのよ?と、、


という訳で、対数を取ったり色々補正項を試してみたが、1固定にした方がよさげ、、
つまり『長い単語ほど価値が高い』を否定した方が良い。。

計算量

今は4-gram , 3-gram , 2-gramの結果をC-VALUEに掛けているが、もう少し長くても良いかな?

ただ積極的にフィルターしていかないとデータ量の爆発についていけなくなる。

今はデータ量的に、Tokenizeの結果に対し、2,3,4-gramを全部あわせて1/10程度まで絞りに絞っている。

MongoDBはDBではない。環境である!

勢いでtwiteしたついでに、軽く書いてみた。

MongoDBのfindAndModifyは物凄く便利で色々使い方があるのだが
$setOnInsertと組み合わせると、お手軽セマフォになるので
こんな感じで簡単にJOB管理に使える訳だ。

全ドキュメントを並列に処理する例

このスクリプトをmongo shell をいくつも立てて実行すれば、同一ドキュメントの重複処理を上手く回避して並列処理できるわけ。

foo
処理対照コレクション
foo_job
ジョブ管理用コレクション
// そのドキュメントが処理中か否か判定する
function isVacant(id){
  var prev = db.foo_job.findAndModify({
    query: {_id:id},
    update:{ $setOnInsert:{ tm:ISODate()}},
    upsert:true
  });
  if ( prev ) {
    return false;
  }
  return true;
}

// コレクション全部まわして、誰かが処理中のドキュメントはスキップ
var _c_foo = db.foo.find({});
  while ( _c_foo.hasNext() ) {
    var doc = _c_foo.next();
    if ( ! isVacant(doc._id) ) {
      continue;
    }
        :
    ココからdocを処理
              
  }
}

実際はもうちょっとエレガントに書いて再利用してる。
また、ドキュメントサイズが大きく、更に並列度を高くする場合は

var _c_foo = db.foo.find({},{_id:1});  

として、判定までは_idしか取って来なければ良い。
その後、自分が処理すると解った段階でfindOneで改めて取ってくると。。

色々やってみた所、MongoDBではIndexを使ったクエリーであっても性能に対する影響度は

データ量 << クエリーか良い数

のようで、

この辺りは扱うデータ次第。。大抵の場合は上の例で良い筈。


さてココから先はbashの話なので、やりたい人はこの辺りを見てって、、
https://github.com/crumbjp/analysis/blob/master/vectorize/bin/tf.sh


さてこれで、
プライマリノードのホストのCPUは使い切れるようになった。

(読み書きをPRIMARYに行うという意味)

セカンダリで処理する時の問題

セカンダリに接続する場合では、書き込みが出来ないので、その部分だけプライマリに逃がせば、後は一緒。

プライマリへのコネクションの張り方

var _pmongo = db.getMongo();
if ( ! rs.isMaster().ismaster ) {
  for ( var i in rs.status().members ) {
    var member = rs.status().members[i];
    if ( member.state === 1 ) {
      var conn = connect(member.name+'/'+db.getName());
      _pmongo = conn.getMongo();
      rs.slaveOk();
      break;
    }
  }
}

さして難しくないから、解説やーめたw

とにかくコレで_pmongoはプライマリへ向いた。

使うときはこんな感じか、、

  _pmongo.getDB('test').test.stats()

mongo shellを何処で動かすか?

当然、PRIMARYノードのホストからPRIMARYノードに繋げる場合が一番早い。

次に、SECONDARYノードのホストからSECONDARYノードに繋げ、上記の方法でPRIMARYへ書き込みする。

レプリカセット構成ホストの外から繋げてもなんの問題もなく、むしろ並列度を格段に上げる事ができるが、レイテンシーの影響が大きく、処理速度は数分の一になる。
プロセス数を大量に稼がないと元が取れない。

今週末7/28は、大岡山でもんご祭り!

2013納涼もんご祭り

ここの所、これに掛かりきりです。
遊びに来てくださいな!

参加無料!
色々面白い発表あり!!
10gen来るかも?
プロレスラーあり!?

いや、、ホントかよ?
いい大人が悪乗りすると怖いね!!
人とお金集めてホントにやっちゃうから・・・

あ、そうそう。
この日、大岡山は別途お祭りがあるようですよ〜
どっちも楽しめるかも