KNPの固有表現認識モデル学習方法

KNPでも学習データを用意すれば、CaboChaのように固有表現認識モデルを学習させることができる。
学習方法は、ここに書かれている通りだけど、試してみてセグフォに嵌ったのでメモ_φ(・_・

  • JUMAN 7.01
  • KNP 4.16
学習データの用意(アノテーション

学習データは、JUMANの出力に対して<NE:タグ名:ポジション名>のように固有表現アノテーションが付与された形式をとる。
タグは、{"ORGANIZATION", "PERSON", "LOCATION", "ARTIFACT", "DATE", "TIME", "MONEY", "PERCENT"} ポジションは、{"B", "I", "E", "S"} を使う。
実際に作業する際は、KNPの解析結果から形態素行を抜き出して、必要に応じて固有表現を修正する。

$ cat train.txt
昨夜、安倍晋三首相は赤崎氏に電話した
$ cat train.txt | juman | knp -simple | grep ^[^#*+] > train.jmn
$ vim train.jmn
昨夜 さくや 昨夜 名詞 6 時相名詞 10 * 0 * 0 "代表表記:昨夜/さくや カテゴリ:時間" <NE:TIME:S>
、 、 、 特殊 1 読点 2 * 0 * 0 NIL 
安倍 あべ 安倍 名詞 6 人名 5 * 0 * 0 "人名:日本:姓:189:0.00134 疑似代表表記 代表表記:安倍/あべ" <NE:PERSON:B>
晋三 すすむさん 晋三 名詞 6 人名 5 * 0 * 0 "人名:日本:名:427:0.00035 疑似代表表記 代表表記:晋三/すすむさん 品詞変更:晋-すすむ-晋-6-5-0-0 品詞変更:晋-すすむ-晋-6-5-0-0" <NE:PERSON:E>
首相 しゅしょう 首相 名詞 6 普通名詞 1 * 0 * 0 "代表表記:首相/しゅしょう 人名末尾 カテゴリ:人 ドメイン:政治" 
は は は 助詞 9 副助詞 2 * 0 * 0 NIL 
赤 あか 赤 名詞 6 普通名詞 1 * 0 * 0 "代表表記:赤/あか 漢字読み:訓 カテゴリ:色" <NE:PERSON:B>
崎 崎 崎 名詞 6 普通名詞 1 * 0 * 0 "疑似代表表記 代表表記:崎/崎 品詞変更:崎-崎-崎-15-1-0-0" <NE:PERSON:E>
氏 し 氏 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:氏/し" 
に に に 助詞 9 格助詞 1 * 0 * 0 NIL 
電話 でんわ 電話 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:電話/でんわ 補文ト カテゴリ:人工物-その他 ドメイン:家庭・暮らし" 
した した する 動詞 2 * 0 サ変動詞 16 タ形 10 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる" 
EOS
CRF++で学習させる

まず、KNPに固有表現学習オプション(-ne-train)があるので、それを使ってCRF++へ入力する学習データへ変換できる。

$ knp -ne-train < train.jmn 2> train.data
$ cat train.data
昨夜 名詞 時相名詞 漢字 L:2 NIL CT:時間 CS:無格 P:電話 S:昨夜 SINGLE C 123
、 特殊 読点 記号 L:1 NIL CT NIL NIL NIL OTHER C 132
安倍 名詞 人名 漢字 L:2 H:人名末尾 CT CS:未格 P:電話 H:首相 BEGIN C 104
晋三 名詞 人名 漢字 L:2 H:人名末尾 CT CS:未格 P:電話 H:首相 INTER C 106
首相 名詞 普通名詞 漢字 L:2 S:人名末尾 CT:人 CS:未格 P:電話 S:首相 END C 132
は 助詞 副助詞 ひらがな L:1 NIL CT NIL NIL NIL OTHER C 132
赤 名詞 普通名詞 漢字 L:1 NIL CT:色 CS:ニ格 P:電話 H:崎 BEGIN C 104
崎 名詞 普通名詞 漢字 L:1 NIL CT CS:ニ格 P:電話 S:崎 END C 106
氏 接尾辞 名詞性名詞接尾辞 漢字 L:1 NIL CT NIL NIL NIL OTHER C 132
に 助詞 格助詞 ひらがな L:1 NIL CT NIL NIL NIL OTHER C 132
電話 名詞 サ変名詞 漢字 L:2 NIL CT:人工物-その他 NIL NIL S:電話 SINGLE C 132
した 動詞 動詞 ひらがな L:2 NIL CT NIL NIL NIL OTHER C 132

学習データが生成されたら、KNPのソースに含まれている素性テンプレート(knp-4.16/crf/template)とCRF++を使って学習させる。

$ crf_learn -f 1 template train.data crf.model
CRF++: Yet Another CRF Tool Kit
Copyright (C) 2005-2013 Taku Kudo, All rights reserved.

reading training data: 
Done!0.00 s

Number of sentences: 1
Number of features:  4128
Number of thread(s): 1
Freq:                1
eta:                 0.00010
C:                   1.00000
shrinking size:      20
iter=0 terr=0.83333 serr=1.00000 act=4128 obj=16.63553 diff=1.00000
iter=1 terr=0.16667 serr=1.00000 act=4128 obj=4.17870 diff=0.74881
iter=2 terr=0.00000 serr=0.00000 act=4128 obj=1.14238 diff=0.72662
iter=3 terr=0.00000 serr=0.00000 act=4128 obj=1.12581 diff=0.01450
iter=4 terr=0.00000 serr=0.00000 act=4128 obj=1.06598 diff=0.05314
iter=5 terr=0.00000 serr=0.00000 act=4128 obj=1.05841 diff=0.00710
iter=6 terr=0.00000 serr=0.00000 act=4128 obj=1.05495 diff=0.00327
iter=7 terr=0.00000 serr=0.00000 act=4128 obj=1.05394 diff=0.00095
iter=8 terr=0.00000 serr=0.00000 act=4128 obj=1.05353 diff=0.00039
iter=9 terr=0.00000 serr=0.00000 act=4128 obj=1.05341 diff=0.00011
iter=10 terr=0.00000 serr=0.00000 act=4128 obj=1.05331 diff=0.00010
iter=11 terr=0.00000 serr=0.00000 act=4128 obj=1.05331 diff=0.00000
iter=12 terr=0.00000 serr=0.00000 act=4128 obj=1.05330 diff=0.00000

Done!0.00 s
動作確認

生成されたモデルを使うようにknprcを変更する。

$ cp crf.model /usr/local/share/knp/dict/mycrf.model
$ vim /usr/local/etc/knprc
 :
(NEモデルファイル
;        /usr/local/share/knp/dict/crf.model
        /usr/local/share/knp/dict/mycrf.model
)
 :

試しに学習したテキストを解析させてみると Segmentation Fault になってしまう。

$ cat train.txt | juman | knp -simple
Segmentation fault (core dumped)

デバッグしてみたところ、全てのラベル候補の周辺確率を取得しようとしているので、一回も学習させていないラベルがあるとエラーになってしまうだけだった。
104(PERSON:B)、106(PERSON:E)、123(TIME:S)、132(OTHER)しか学習してないから。

crf_test -v2 -m crf.model train.data 
# 0.725886
昨夜	名詞	時相名詞	漢字	L:2	NIL	CT:時間	CS:無格	P:電話	S:昨夜	SINGLE	C	123	123/0.966497	104/0.009152	106/0.009384	123/0.966497	132/0.014967
、	特殊	読点	記号	L:1	NIL	CT	NIL	NIL	NIL	OTHER	C	132	132/0.976693	104/0.008768	106/0.007523	123/0.007016	132/0.976693
安倍	名詞	人名	漢字	L:2	H:人名末尾	CT	CS:未格	P:電話	H:首相	BEGIN	C	104	104/0.968226	104/0.968226	106/0.007983	123/0.006219	132/0.017572
晋三	名詞	人名	漢字	L:2	H:人名末尾	CT	CS:未格	P:電話	H:首相	INTER	C	106	106/0.969562	104/0.009320	106/0.969562	123/0.005628	132/0.015490
首相	名詞	普通名詞	漢字	L:2	S:人名末尾	CT:人	CS:未格	P:電話	S:首相	END	C	132	132/0.975734	104/0.007709	106/0.009869	123/0.006688	132/0.975734
は	助詞	副助詞	ひらがな	L:1	NIL	CT	NIL	NIL	NIL	OTHER	C	132	132/0.981199	104/0.007942	106/0.006335	123/0.004524	132/0.981199
赤	名詞	普通名詞	漢字	L:1	NIL	CT:色	CS:ニ格	P:電話	H:崎	BEGIN	C	104	104/0.966110	104/0.966110	106/0.010123	123/0.005958	132/0.017809
崎	名詞	普通名詞	漢字	L:1	NIL	CT	CS:ニ格	P:電話	S:崎	END	C	106	106/0.964598	104/0.007980	106/0.964598	123/0.005982	132/0.021440
氏	接尾辞	名詞性名詞接尾辞	漢字	L:1	NIL	CT	NIL	NIL	NIL	OTHER	C	132	132/0.980925	104/0.006594	106/0.007754	123/0.004727	132/0.980925
に	助詞	格助詞	ひらがな	L:1	NIL	CT	NIL	NIL	NIL	OTHER	C	132	132/0.979295	104/0.008626	106/0.007978	123/0.004101	132/0.979295
電話	名詞	サ変名詞	漢字	L:2	NIL	CT:人工物-その他	NIL	NIL	S:電話	SINGLE	C	132	132/0.976011	104/0.009213	106/0.008686	123/0.006089	132/0.976011
した	動詞	動詞	ひらがな	L:2	NIL	CT	NIL	NIL	NIL	OTHER	C	132	132/0.978093	104/0.006737	106/0.009539	123/0.005631	132/0.978093
学習データの追加

強引にラベルを一通り用意する。

昨夜 さくや 昨夜 名詞 6 時相名詞 10 * 0 * 0 "代表表記:昨夜/さくや カテゴリ:時間" <NE:TIME:S>
、 、 、 特殊 1 読点 2 * 0 * 0 NIL
安倍 あべ 安倍 名詞 6 人名 5 * 0 * 0 "人名:日本:姓:189:0.00134 疑似代表表記 代表表記:安倍/あべ" <NE:PERSON:B>
晋三 すすむさん 晋三 名詞 6 人名 5 * 0 * 0 "人名:日本:名:427:0.00035 疑似代表表記 代表表記:晋三/すすむさん 品詞変更:晋-すすむ-晋-6-5-0-0 品詞変更:晋-すすむ-晋-6-5-0-0" <NE:PERSON:E>
首相 しゅしょう 首相 名詞 6 普通名詞 1 * 0 * 0 "代表表記:首相/しゅしょう 人名末尾 カテゴリ:人 ドメイン:政治"
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
赤 あか 赤 名詞 6 普通名詞 1 * 0 * 0 "代表表記:赤/あか 漢字読み:訓 カテゴリ:色" <NE:PERSON:B>
崎 崎 崎 名詞 6 普通名詞 1 * 0 * 0 "疑似代表表記 代表表記:崎/崎 品詞変更:崎-崎-崎-15-1-0-0" <NE:PERSON:E>
氏 し 氏 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:氏/し"
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
電話 でんわ 電話 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:電話/でんわ 補文ト カテゴリ:人工物-その他 ドメイン:家庭・暮らし"
した した する 動詞 2 * 0 サ変動詞 16 タ形 10 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
EOS
太郎 たろう 太郎 名詞 6 人名 5 * 0 * 0 "人名:日本:名:45:0.00106 疑似代表表記 代表表記:太郎/たろう" <NE:PERSON:S>
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
朝 あさ 朝 名詞 6 時相名詞 10 * 0 * 0 "代表表記:朝/あさ 漢字読み:訓 カテゴリ:時間" <NE:TIME:B>
9 きゅう 9 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:9/きゅう" <NE:TIME:I>
時 じ 時 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:時/じ 準内容語 カテゴリ:時間" <NE:TIME:E>
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
花子 はなこ 花子 名詞 6 人名 5 * 0 * 0 "人名:日本:名:912:0.00019 疑似代表表記 代表表記:花子/はなこ" <NE:LOCATION:S>
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
会い あい 会う 動詞 2 * 0 子音動詞ワ行 12 基本連用形 8 "代表表記:会う/あう 反義:動詞:分かれる/わかれる;動詞:別れる/わかれる"
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
行った いった 行く 動詞 2 * 0 子音動詞カ行促音便形 3 タ形 10 "代表表記:行く/いく 付属動詞候補(タ系) ドメイン:交通 反義:動詞:帰る/かえる"
EOS
藤本 ふじもと 藤本 名詞 6 人名 5 * 0 * 0 "人名:日本:姓:167:0.00147 疑似代表表記 代表表記:藤本/ふじもと" <NE:PERSON:B>
太郎 たろう 太郎 名詞 6 人名 5 * 0 * 0 "人名:日本:名:45:0.00106 疑似代表表記 代表表記:太郎/たろう" <NE:PERSON:I>
喜 喜 喜 名詞 6 普通名詞 1 * 0 * 0 "疑似代表表記 代表表記:喜/喜 品詞変更:喜-喜-喜-15-1-0-0" <NE:PERSON:I>
左 ひだり 左 名詞 6 普通名詞 1 * 0 * 0 "代表表記:左/ひだり 漢字読み:訓 カテゴリ:場所-機能" <NE:PERSON:I>
衛 まもる 衛 名詞 6 人名 5 * 0 * 0 "人名:日本:名:628:0.00025 疑似代表表記 代表表記:衛/まもる" <NE:PERSON:I>
門 もん 門 名詞 6 普通名詞 1 * 0 * 0 "代表表記:門/もん 漢字読み:音 カテゴリ:場所-施設" <NE:PERSON:I>
将 すすむ 将 名詞 6 人名 5 * 0 * 0 "人名:日本:名:1439:0.00012 疑似代表表記 代表表記:将/すすむ" <NE:PERSON:I>
時 じ 時 名詞 6 時相名詞 10 * 0 * 0 "代表表記:時/じ 漢字読み:音 弱時相名詞 カテゴリ:時間" <NE:PERSON:I>
能 のう 能 名詞 6 普通名詞 1 * 0 * 0 "代表表記:能/のう 漢字読み:音 カテゴリ:抽象物" <NE:PERSON:E>
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
いう いう いう 動詞 2 * 0 子音動詞ワ行 12 基本形 2 "代表表記:言う/いう 補文ト"
名前 なまえ 名前 名詞 6 普通名詞 1 * 0 * 0 "代表表記:名前/なまえ カテゴリ:抽象物"
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL
人 じん 人 名詞 6 普通名詞 1 * 0 * 0 "代表表記:人/じん 漢字読み:音 カテゴリ:人"
が が が 助詞 9 格助詞 1 * 0 * 0 NIL
いる いる いる 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:煎る/いる ドメイン:料理・食事"
らしい らしい らしい 助動詞 5 * 0 イ形容詞イ段 19 基本形 2 NIL
EOS
今年 ことし 今年 名詞 6 時相名詞 10 * 0 * 0 "代表表記:今年/ことし カテゴリ:時間" <NE:DATE:S>
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL
人工 じんこう 人工 名詞 6 普通名詞 1 * 0 * 0 "代表表記:人工/じんこう カテゴリ:抽象物" <NE:ORGANIZATION:B>
知能 ちのう 知能 名詞 6 普通名詞 1 * 0 * 0 "代表表記:知能/ちのう カテゴリ:抽象物" <NE:ORGANIZATION:I>
学会 がっかい 学会 名詞 6 普通名詞 1 * 0 * 0 "代表表記:学会/がっかい 組織名末尾 カテゴリ:組織・団体;抽象物 ドメイン:科学・技術" <Wikipedia上位語:学会/がっかい:2-4><Wikipediaエ>ントリ:人工知能学会:2-4> <NE:ORGANIZATION:E>
全国 ぜんこく 全国 名詞 6 普通名詞 1 * 0 * 0 "代表表記:全国/ぜんこく カテゴリ:場所-その他"
大会 たいかい 大会 名詞 6 普通名詞 1 * 0 * 0 "代表表記:大会/たいかい カテゴリ:抽象物" <Wikipediaエントリ:全国大会:5-6>
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
2016 にぜろいちろく 2016 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:2016/にぜろいちろく" <NE:DATE:B>
年 ねん 年 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:年/ねん 準内容語 カテゴリ:時間" <NE:DATE:I>
6 ろく 6 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:6/ろく" <NE:DATE:I>
月 がつ 月 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:月/がつ 準内容語 カテゴリ:時間" <NE:DATE:I>
6 ろく 6 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:6/ろく" <NE:DATE:I>
日 にち 日 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:日/にち 準内容語 カテゴリ:時間" <NE:DATE:E>
〜 〜 〜 特殊 1 記号 5 * 0 * 0 NIL
9 きゅう 9 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:9/きゅう" <NE:DATE:B>
日 にち 日 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:日/にち 準内容語 カテゴリ:時間" <NE:DATE:E>
まで まで まで 助詞 9 格助詞 1 * 0 * 0 NIL
北九州 きたきゅう 北九州 名詞 6 地名 4 * 0 * 0 "代表表記:北九州/きたきゅう 地名:日本:福岡県:市" <NE:LOCATION:B>
国際 こくさい 国際 名詞 6 普通名詞 1 * 0 * 0 "代表表記:国際/こくさい カテゴリ:抽象物" <NE:LOCATION:I>
会議 かいぎ 会議 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:会議/かいぎ 組織名末尾 カテゴリ:抽象物" <NE:LOCATION:I>
場 ば 場 名詞 6 普通名詞 1 * 0 * 0 "代表表記:場/ば 漢字読み:訓 カテゴリ:場所-その他;抽象物" <Wikipedia上位語:コンベンションセンター:18-21><Wikipediaエントリ:北九州国際会議場:18-21> <NE:LOCATION:E>
で で で 助詞 9 格助詞 1 * 0 * 0 NIL
開催 かいさい 開催 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:開催/かいさい カテゴリ:抽象物"
さ さ する 動詞 2 * 0 サ変動詞 16 未然形 3 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
れ れ れる 接尾辞 14 動詞性接尾辞 7 母音動詞 1 基本連用形 8 "代表表記:れる/れる"
ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます"
EOS
この前 このまえ この前 名詞 6 時相名詞 10 * 0 * 0 "代表表記:此前/このまえ カテゴリ:時間"
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL
祝日 しゅくじつ 祝日 名詞 6 普通名詞 1 * 0 * 0 "代表表記:祝日/しゅくじつ カテゴリ:時間" <NE:DATE:S>
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
原野 げんや 原野 名詞 6 普通名詞 1 * 0 * 0 "代表表記:原野/げんや カテゴリ:場所-自然" <NE:LOCATION:B>
谷川 たにがわ 谷川 名詞 6 普通名詞 1 * 0 * 0 "代表表記:谷川/たにがわ カテゴリ:場所-自然" <Wikipedia上位語:河川/かせん:4-5><Wikipediaエントリ:原野谷川:4-5><NE:LOCATION:I>
親水 親水 親水 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:テキスト 疑似代表表記 代表表記:親水/親水" <NE:LOCATION:I>
公園 こうえん 公園 名詞 6 普通名詞 1 * 0 * 0 "代表表記:公園/こうえん カテゴリ:場所-施設 ドメイン:レクリエーション" <Wikipediaエントリ:親水公園:6-7><NE:LOCATION:E>
で で で 助詞 9 格助詞 1 * 0 * 0 NIL
バーベキュー ばーべきゅー バーベキュー 名詞 6 普通名詞 1 * 0 * 0 "代表表記:バーベキュー/ばーべきゅー カテゴリ:人工物-食べ物 ドメイン:料理・食事"
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
した した する 動詞 2 * 0 サ変動詞 16 タ形 10 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
EOS
この前 このまえ この前 名詞 6 時相名詞 10 * 0 * 0 "代表表記:此前/このまえ カテゴリ:時間"
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL
祝日 しゅくじつ 祝日 名詞 6 普通名詞 1 * 0 * 0 "代表表記:祝日/しゅくじつ カテゴリ:時間" <NE:DATE:S>
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
原野 げんや 原野 名詞 6 普通名詞 1 * 0 * 0 "代表表記:原野/げんや カテゴリ:場所-自然" <NE:LOCATION:B>
谷川 たにがわ 谷川 名詞 6 普通名詞 1 * 0 * 0 "代表表記:谷川/たにがわ カテゴリ:場所-自然" <Wikipedia上位語:河川/かせん:4-5><Wikipediaエントリ:原野谷川:4-5><NE:LOCATION:I>
親水 親水 親水 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:テキスト 疑似代表表記 代表表記:親水/親水" <NE:LOCATION:I>
公園 こうえん 公園 名詞 6 普通名詞 1 * 0 * 0 "代表表記:公園/こうえん カテゴリ:場所-施設 ドメイン:レクリエーション" <Wikipediaエントリ:親水公園:6-7><NE:LOCATION:E>
で で で 助詞 9 格助詞 1 * 0 * 0 NIL
バーベキュー ばーべきゅー バーベキュー 名詞 6 普通名詞 1 * 0 * 0 "代表表記:バーベキュー/ばーべきゅー カテゴリ:人工物-食べ物 ドメイン:料理・食事"
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
した した する 動詞 2 * 0 サ変動詞 16 タ形 10 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
EOS
掛川 かけがわ 掛川 名詞 6 地名 4 * 0 * 0 "代表表記:掛川/かけがわ 地名:日本:静岡県:市" <NE:LOCATION:S>
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
こだま こだま こだま 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:木霊/こだま カテゴリ:抽象物" <NE:ARTIFACT:S>
しか しか しか 助詞 9 副助詞 2 * 0 * 0 NIL
止まら とまら 止まる 動詞 2 * 0 子音動詞ラ行 10 未然形 3 "代表表記:止まる/とまる 自他動詞:他:止める/とめる 反義:動詞:動く/うごく"
ない ない ない 接尾辞 14 形容詞性述語接尾辞 5 イ形容詞アウオ段 18 基本形 2 "代表表記:ない/ない"
EOS
天竜 てんりゅう 天竜 名詞 6 地名 4 * 0 * 0 "代表表記:天竜/てんりゅう 地名:日本:静岡県:区" <NE:ARTIFACT:B>
浜名湖 浜名湖 浜名湖 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:テキスト 疑似代表表記 代表表記:浜名湖/浜名湖" <NE:ARTIFACT:I>
線 せん 線 名詞 6 普通名詞 1 * 0 * 0 "代表表記:線/せん 漢字読み:音 カテゴリ:形・模様" <Wikipediaエントリ:天竜浜名湖線:0-2><NE:ARTIFACT:E>
が が が 助詞 9 格助詞 1 * 0 * 0 NIL
なくなる なくなる なくなる 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:無くなる/なくなる 自他動詞:他:無くす/なくす"
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
意外に いがいに 意外だ 形容詞 3 * 0 ナノ形容詞 22 ダ列基本連用形 8 "代表表記:意外だ/いがいだ"
困る こまる 困る 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:困る/こまる"
EOS
今 こん 今 接頭辞 13 名詞接頭辞 1 * 0 * 0 "代表表記:今/こん 内容語"
シーズン しーずん シーズン 名詞 6 時相名詞 10 * 0 * 0 "代表表記:シーズン/しーずん 弱時相名詞 カテゴリ:時間"
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL
打率 だりつ 打率 名詞 6 普通名詞 1 * 0 * 0 "代表表記:打率/だりつ カテゴリ:数量 ドメイン:スポーツ"
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
3 さん 3 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:3/さん" <NE:PERCENT:B>
割 わり 割 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:割/わり 準内容語" <NE:PERCENT:I>
5 ご 5 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:5/ご" <NE:PERCENT:I>
分 ふん 分 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:分/ふん 準内容語 カテゴリ:時間" <NE:PERCENT:I>
5 ご 5 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:5/ご" <NE:PERCENT:I>
厘 りん 厘 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:厘/りん 準内容語" <NE:PERCENT:E>
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
まあまあ まあまあ まあまあだ 形容詞 3 * 0 ナノ形容詞 22 語幹 1 "代表表記:まあまあ/まあまあa 代表表記変更:まあまあだ/まあまあだ"
調子 ちょうし 調子 名詞 6 普通名詞 1 * 0 * 0 "代表表記:調子/ちょうし カテゴリ:抽象物"
が が が 助詞 9 格助詞 1 * 0 * 0 NIL
いい いい いい 形容詞 3 * 0 イ形容詞イ段 19 基本形 2 "代表表記:良い/よい 反義:形容詞:悪い/わるい"
EOS
バナナ ばなな バナナ 名詞 6 普通名詞 1 * 0 * 0 "代表表記:バナナ/ばなな カテゴリ:植物;人工物-食べ物 ドメイン:料理・食事"
半分 はんぶん 半分 名詞 6 普通名詞 1 * 0 * 0 "代表表記:半分/はんぶん カテゴリ:数量" <NE:PERCENT:S>
ちょうだいな ちょうだいな ちょうだいだ 形容詞 3 * 0 ナ形容詞 21 ダ列基本連体形 3 "代表表記:長大だ/ちょうだいだ"
EOS
参加 さんか 参加 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:参加/さんか カテゴリ:抽象物"
費 ひ 費 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:費/ひ 内容語 カテゴリ:人工物-金銭 同義:名詞:費用/ひよう"
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
して して する 動詞 2 * 0 サ変動詞 16 タ系連用テ形 14 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
1万 いちまん 1万 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:1万/いちまん" <NE:MONEY:S>
いただき いただき いただく 動詞 2 * 0 子音動詞カ行 2 基本連用形 8 "代表表記:頂く/いただく 付属動詞候補(タ系) 謙譲動詞:貰う/もらう;食べる/たべる;飲む/のむ"
ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます"
EOS
明日 あす 明日 名詞 6 時相名詞 10 * 0 * 0 "代表表記:明日/あす カテゴリ:時間"
まで まで まで 助詞 9 格助詞 1 * 0 * 0 NIL
に に に 助詞 9 格助詞 1 * 0 * 0 NIL
100万 いちぜろぜろまん 100万 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:100万/いちぜろぜろまん" <NE:MONEY:B>
香港 ほんこん 香港 名詞 6 地名 4 * 0 * 0 "代表表記:香港/ほんこん 地名:国:中国:特別行政区" <NE:MONEY:I>
ドル どる ドル 名詞 6 普通名詞 1 * 0 * 0 "代表表記:ドル/どる カテゴリ:数量 ドメイン:ビジネス" <Wikipedia上位語:通貨/つうか:4-5><Wikipediaエントリ:香港ドル:4-5> <NE:MONEY:E>
用意 ようい 用意 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:用意/ようい カテゴリ:抽象物"
しろ しろ する 動詞 2 * 0 サ変動詞 16 命令形 6 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
EOS
Google Google Google 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:Wikipedia Wikipedia上位語:検索エンジン 読み不明 疑似代表表記 代表表記:Google/Google" <Wikipedia上位語:検索エンジン> <NE:ORGANIZATION:S>
が が が 助詞 9 格助詞 1 * 0 * 0 NIL
、 、 、 特殊 1 読点 2 * 0 * 0 NIL
「 「 「 特殊 1 括弧始 3 * 0 * 0 NIL
SyntaxNet SyntaxNet SyntaxNet 名詞 6 組織名 6 * 0 * 0 "疑似代表表記 代表表記:SyntaxNet/SyntaxNet 品詞変更:SyntaxNet-Syn
taxNet-SyntaxNet-15-3-0-0" <NE:ARTIFACT:S>
」 」 」 特殊 1 括弧終 4 * 0 * 0 NIL
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
オープンソース オープンソース オープンソース 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:Wikipedia 疑似代表表記 代表表記:オープンソース/オープンソース"
で で で 助詞 9 格助詞 1 * 0 * 0 NIL
公開 こうかい 公開 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:公開/こうかい カテゴリ:抽象物"
した した する 動詞 2 * 0 サ変動詞 16 タ形 10 "代表表記:する/する 付属動詞候補(基本) 自他動詞:自:成る/なる"
EOS

再度モデルを作り直してテスト。

$ knp -ne-train < train.jmn 2> train.data
$ crf_learn -f 1 template train.data crf.model
CRF++: Yet Another CRF Tool Kit
Copyright (C) 2005-2013 Taku Kudo, All rights reserved.

reading training data: 
Done!0.01 s

Number of sentences: 12
Number of features:  213708
Number of thread(s): 1
Freq:                1
eta:                 0.00010
C:                   1.00000
shrinking size:      20
iter=0 terr=0.99275 serr=1.00000 act=213708 obj=482.51804 diff=1.00000
iter=1 terr=0.42029 serr=1.00000 act=213708 obj=257.48250 diff=0.46638
iter=2 terr=0.42029 serr=1.00000 act=213708 obj=221.64201 diff=0.13920
iter=3 terr=0.23913 serr=1.00000 act=213708 obj=149.06598 diff=0.32745
iter=4 terr=0.07246 serr=0.66667 act=213708 obj=83.50011 diff=0.43984
iter=5 terr=0.00000 serr=0.00000 act=213708 obj=28.64027 diff=0.65700
iter=6 terr=0.00000 serr=0.00000 act=213708 obj=24.48020 diff=0.14525
iter=7 terr=0.00000 serr=0.00000 act=213708 obj=22.59193 diff=0.07713
iter=8 terr=0.00000 serr=0.00000 act=213708 obj=21.50966 diff=0.04791
iter=9 terr=0.00000 serr=0.00000 act=213708 obj=20.50080 diff=0.04690
iter=10 terr=0.00000 serr=0.00000 act=213708 obj=19.63163 diff=0.04240
iter=11 terr=0.00000 serr=0.00000 act=213708 obj=18.92093 diff=0.03620
iter=12 terr=0.00000 serr=0.00000 act=213708 obj=18.43905 diff=0.02547
iter=13 terr=0.00000 serr=0.00000 act=213708 obj=18.27158 diff=0.00908
iter=14 terr=0.00000 serr=0.00000 act=213708 obj=18.21956 diff=0.00285
iter=15 terr=0.00000 serr=0.00000 act=213708 obj=18.16238 diff=0.00314
iter=16 terr=0.00000 serr=0.00000 act=213708 obj=18.13418 diff=0.00155
iter=17 terr=0.00000 serr=0.00000 act=213708 obj=18.11071 diff=0.00129
iter=18 terr=0.00000 serr=0.00000 act=213708 obj=18.09165 diff=0.00105
iter=19 terr=0.00000 serr=0.00000 act=213708 obj=18.08471 diff=0.00038
iter=20 terr=0.00000 serr=0.00000 act=213708 obj=18.08399 diff=0.00004
iter=21 terr=0.00000 serr=0.00000 act=213708 obj=18.08047 diff=0.00019
iter=22 terr=0.00000 serr=0.00000 act=213708 obj=18.07992 diff=0.00003
iter=23 terr=0.00000 serr=0.00000 act=213708 obj=18.07892 diff=0.00006
iter=24 terr=0.00000 serr=0.00000 act=213708 obj=18.07867 diff=0.00001

Done!1.56 s
$ vim test.txt 
大谷選手の今シーズンの打率は3割4分8厘と調子がいい
$ cat test.txt | juman | knp -simple
# S-ID:1 KNP:4.16-CF1.1 DATE:2016/05/22 SCORE:-30.36771
* 2D <体言><係:ノ格>
+ 1D <係:文節内><体言>
大谷 おおたに 大谷 名詞 6 人名 5 * 0 * 0 "人名:日本:姓:199:0.00129 疑似代表表記 代表表記:大谷/おおたに" 
+ 4D <体言><係:ノ格>
選手 せんしゅ 選手 名詞 6 普通名詞 1 * 0 * 0 "代表表記:選手/せんしゅ 人名末尾 カテゴリ:人 ドメイン:スポーツ" 
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL 
* 2D <体言><係:ノ格>
+ 3D <係:文節内>
今 こん 今 接頭辞 13 名詞接頭辞 1 * 0 * 0 "代表表記:今/こん 内容語" 
+ 4D <体言><係:ノ格>
シーズン しーずん シーズン 名詞 6 時相名詞 10 * 0 * 0 "代表表記:シーズン/しーずん 弱時相名詞 カテゴリ:時間" 
の の の 助詞 9 接続助詞 3 * 0 * 0 NIL 
* 7D <体言><係:未格>
+ 9D <体言><係:未格>
打率 だりつ 打率 名詞 6 普通名詞 1 * 0 * 0 "代表表記:打率/だりつ カテゴリ:数量 ドメイン:スポーツ" 
は は は 助詞 9 副助詞 2 * 0 * 0 NIL 
* 4D <体言><係:隣>
+ 6D <体言><係:隣><NE内:PERCENT>
3 さん 3 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:3/さん" <NE:PERCENT:B>
割 わり 割 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:割/わり 準内容語" <NE:PERCENT:I>
* 6D <体言><係:隣>
+ 8D <体言><係:隣><NE内:PERCENT>
4 よん 4 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:4/よん" <NE:PERCENT:I>
分 ふん 分 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:分/ふん 準内容語 カテゴリ:時間" <NE:PERCENT:I>
* 6P <体言><係:ト格>
+ 8P <体言><係:ト格><NE:PERCENT:3割4分8厘>
8 はち 8 名詞 6 数詞 7 * 0 * 0 "カテゴリ:数量 疑似代表表記 代表表記:8/はち" <NE:PERCENT:I>
厘 りん 厘 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:厘/りん 準内容語" <NE:PERCENT:E>
と と と 助詞 9 格助詞 1 * 0 * 0 NIL 
* 7D <体言><係:ガ格>
+ 9D <体言><係:ガ格>
調子 ちょうし 調子 名詞 6 普通名詞 1 * 0 * 0 "代表表記:調子/ちょうし カテゴリ:抽象物" 
が が が 助詞 9 格助詞 1 * 0 * 0 NIL 
* -1D <用言:形><レベル:C><ID:(文末)>
+ -1D <用言:形><レベル:C><ID:(文末)><格解析結果:良い/よい:形4:ガ/C/厘/7/0/1;ガ/C/調子/8/0/1;ニ/U/-/-/-/-;ト/U/-/-/-/-;デ/U/-/-/-/-;カラ/U/-/-/-/-;ヨリ/U/-/-/-/-;マデ/U/-/-/-/-;時間/U/-/-/-/-;外の関係/U/-/-/-/-;修飾/U/-/-/-/-;ノ/U/-/-/-/-;ガ2/N/打率/4/0/1;トスル/U/-/-/-/-;ニカギル/U/-/-/-/-;ニクラベル/U/-/-/-/-;ニツヅク/U/-/-/-/-;ヲツウジル/U/-/-/-/-;ニオク/U/-/-/-/->
いい いい いい 形容詞 3 * 0 イ形容詞イ段 19 基本形 2 "代表表記:良い/よい 反義:形容詞:悪い/わるい" 
EOS

動くようになった。

おしまい

付属のモデルよりドメイン特化してでも精度改善したい場合は、KNPの解析結果を修正する以外に、これとかネタに使えばいいのかな?

ValueError: numpy.dtype has the wrong size, try recompiling

scikit-learnとかpandas環境のセットアップ作業で、手間取ってnumpyのバージョン動かしてると出るエラー。
単純に再インストールしてもキャッシュを見てビルドされないらしー。

$ pip uninstall scikit-learn
$ pip install scikit-learn==0.16.1 --no-cache-dir

とやる_φ(・_・

係り受け解析器の学習機能

もう少し自然言語処理の意味理解よりのことをちゃんと勉強してみようと思う今日この頃。のやったことメモ_φ(・_・
今回は、係り受け解析器のうち、CaboChaとJ.DepP の学習機能を動かしてみたのでその辺の備忘メモ

準備

CaboChaは既にインストール済み。
モデルは今回、KNBコーパスから学習させるので品詞体系をJUMANにしておく。モデル(係り受け、チャンキング、固有表現)もとりあえずJUMANに変更。

$ cat /usr/local/etc/cabocharc 
 :
posset = JUMAN
 :
# Parser model file name
parser-model  = /usr/local/lib/cabocha/model/dep.juman.model
 :
# Chunker model file name
chunker-model = /usr/local/lib/cabocha/model/chunk.juman.model
 :
# NE model file name
ne-model = /usr/local/lib/cabocha/model/ne.juman.model
 :

MeCabのデフォルト辞書もJUMANに変更(辞書のインストールは既に済み)

$ cat /usr/local/etc/mecabrc
 :
dicdir = /usr/local/lib/mecab/dic/jumandic
 :

動作確認

$ echo "クロールで泳いでいる少女を見た" | cabocha -f 2 -n 1
dep.cpp(90) [decode_posset(p) == posset()] model posset and dependency parser's posset are different: JUMAN != IPA

なぜか品詞体系がIPAのまま?実行時オプションは利いたので、とりあえず今はスルー

$ echo "クロールで泳いでいる少女を見た" | cabocha -P JUMAN -f 2 -n 1
クロールで-D    
  泳いでいる-D  
        少女を-D
            見た
EOS
* 0 1D 0/1 1.421559
クロール	名詞,普通名詞,*,*,クロール,くろーる,代表表記:クロール/くろーる カテゴリ:抽象物 ドメイン:スポーツ	O
で	助詞,格助詞,*,*,で,で,*	O
* 1 2D 0/1 1.458612
泳いで	動詞,*,子音動詞ガ行,タ系連用テ形,泳ぐ,およいで,代表表記:泳ぐ/およぐ	O
いる	接尾辞,動詞性接尾辞,母音動詞,基本形,いる,いる,連語	O
* 2 3D 0/1 1.458612
少女	名詞,普通名詞,*,*,少女,しょうじょ,代表表記:少女/しょうじょ カテゴリ:人	O
を	助詞,格助詞,*,*,を,を,*	O
* 3 -1D 0/0 0.000000
見た	動詞,*,母音動詞,タ形,見る,みた,代表表記:見る/みる 補文ト 自他動詞:自:見える/みえる	O
EOS

続いてJ.DepP。 聞いたことなかったけど、CaboCha同様にコーパスから学習させることができるようなので入れてみた。学習、解析速度が速く、学習したモデルの精度もSVMに匹敵するらしい。
こちらは、デフォルトでMeCab、JUMAN辞書、モデルの学習にKNBコーパスを使うようになってる。

$ wget http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/jdepp/jdepp-latest.tar.gz
$ tar xzf jdepp-latest.tar.gz 
$ cd jdepp-2015-10-05/
$ ./configure
$ make model
$ sudo make install
$ echo "クロールで泳いでいる少女を見た" | mecab | jdepp
(input: STDIN [-I 0])
# S-ID: 1; J.DepP
* 0 1D
クロール	名詞,普通名詞,*,*,クロール,くろーる,代表表記:クロール/くろーる カテゴリ:抽象物 ドメイン:スポーツ
で	助詞,格助詞,*,*,で,で,*
* 1 2D
泳いで	動詞,*,子音動詞ガ行,タ系連用テ形,泳ぐ,およいで,代表表記:泳ぐ/およぐ
いる	接尾辞,動詞性接尾辞,母音動詞,基本形,いる,いる,連語
* 2 3D
少女	名詞,普通名詞,*,*,少女,しょうじょ,代表表記:少女/しょうじょ カテゴリ:人
を	助詞,格助詞,*,*,を,を,*
* 3 -1D
見た	動詞,*,母音動詞,タ形,見る,みた,代表表記:見る/みる 補文ト 自他動詞:自:見える/みえる
EOS

J.DepP profiler:
io        : 0.0166 ms./trial (0.06638521/4)
dict      : 0.3317 ms.
preproc   : 0.0194 ms.
chunk     : 0.0536 ms.
depnd     : 0.0250 ms.

ついでに、KNPも入れた。解析速度とか今のところ大規模なテキスト処理は厳しいけど、70億分のWebテキストから自動構築した格フレームとかいろいろ興味深いところもあるので、また別途時間をとっていろいろ確認したい。

$ tar xjf juman-7.01.tar.bz2 
$ cd juman-7.01
$ ./configure --prefix=/usr/local
$ make
$ sudo make install
$ echo "クロールで泳いでいる少女を見た" | juman
クロール くろーる クロール 名詞 6 普通名詞 1 * 0 * 0 "代表表記:クロール/くろーる カテゴリ:抽象物 ドメイン:スポーツ"
で で で 助詞 9 格助詞 1 * 0 * 0 NIL
泳いで およいで 泳ぐ 動詞 2 * 0 子音動詞ガ行 4 タ系連用テ形 14 "代表表記:泳ぐ/およぐ"
いる いる いる 接尾辞 14 動詞性接尾辞 7 母音動詞 1 基本形 2 "代表表記:いる/いる"
少女 しょうじょ 少女 名詞 6 普通名詞 1 * 0 * 0 "代表表記:少女/しょうじょ カテゴリ:人"
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
見た みた 見る 動詞 2 * 0 母音動詞 1 タ形 10 "代表表記:見る/みる 補文ト 自他動詞:自:見える/みえる"
EOS
$ tar xjf knp-4.15.tar.bz2
$ cd knp-4.15
$ ./configure --prefix=/usr/local --with-juman-prefix=/usr/local/
$ make
$ sudo make install
$ echo "クロールで泳いでいる少女を見た" | juman | knp
# S-ID:1 KNP:4.15-CF1.1 DATE:2015/11/26 SCORE:-19.12316
クロールで──┐         
        泳いでいる──┐     
                    少女を──┐ 
                              見た
EOS
CaboChaのモデルをKNBコーパスから作る

CaboChaの学習/評価手順の確認。今回は手軽につかえるKNBコーパスを使ってチャンキングと係り受けモデルを作成する。
4ジャンルに分かれているので、単純に1つを評価用、残りの3つを学習用として作業する。

入力フォーマットは、CaboCha形式に変換する必要があるが、後述のJ.DepP付属のスクリプトがほぼそのまま使えるのでそちらを調整(ヘッダを消しただけ)して使わせてもらう。

評価用のスクリプトeval.py)とかもJ.DepP付属のものを使用している。
最後に、各ジャンルの評価結果を↓でガッちゃんこしただけ


以下、コーパスの作成からモデルの作成、テスト、評価までの作業メモ

# KNBコーパスの変換
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Keitai*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc1.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Kyoto*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc2.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Gourmet*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc3.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Sports*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc4.euc

$ for i in `seq 1 1 4`;
> do
>   iconv -f EUC-JP -t UTF-8 knbc$i.euc > knbc$i.utf8;
> done

# 学習用と評価用に分ける
$ cat knbc2.utf8 knbc3.utf8 knbc4.utf8> corpus1
$ cat knbc1.utf8 > gold1
$ cat knbc3.utf8 knbc4.utf8 knbc1.utf8> corpus2
$ cat knbc2.utf8 > gold2
$ cat knbc4.utf8 knbc1.utf8 knbc2.utf8> corpus3
$ cat knbc3.utf8 > gold3
$ cat knbc1.utf8 knbc2.utf8 knbc3.utf8> corpus4
$ cat knbc4.utf8 > gold4

# 文節と係り受けを学習させてそのまま評価
$ for i in `seq 1 1 4`;
> do
>   /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN -t utf-8 corpus$i chunk$i.model;
>   /usr/local/libexec/cabocha/cabocha-learn -e dep -P JUMAN -t utf-8 corpus$i dep$i.model
>   cat gold$i | python ../tools/to_sent.py | mecab -d /usr/local/lib/mecab/dic/jumandic | cabocha -m dep$i.model -M chunk$i.model -P JUMAN -I1 -f1 > result$i
> done

$ for i in `seq 1 1 4`;
> do
>   python ../tools/eval.py result$i gold$i 2> eval$i;
> done

# 評価結果をガッちゃんこ
$ cat eval[1-4] | python ../tools/eval_all.py 
chunk:
  precision: 0.8221 (22713/27628)
  recall:    0.8189 (22713/27737)
  f1:        0.8205
  sent acc.: 0.4853 (2031/4185)
depnd:
  precision: 0.6288 (14742/23443)
  recall:    0.6259 (14742/23552)
  f1:        0.6274
  sent acc.: 0.3642 (1524/4185)

あんまりよくない気が、、、そもそもMeCabを学習させてないからこんなもんか??

J.DepPのモデルをKNBコーパスから作る

同じことをJ.DepPでもやってみる。

$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Keitai*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc1.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Kyoto*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc2.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Gourmet*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc3.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Sports*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc4.euc

$ for i in `seq 1 1 4`;
> do
>   iconv -f EUC-JP -t UTF-8 knbc$i.euc > knbc$i.utf8;
> done

$ cat knbc2.utf8 knbc3.utf8 knbc4.utf8> corpus1
$ cat knbc1.utf8 > gold1
$ cat knbc3.utf8 knbc4.utf8 knbc1.utf8> corpus2
$ cat knbc2.utf8 > gold2
$ cat knbc4.utf8 knbc1.utf8 knbc2.utf8> corpus3
$ cat knbc3.utf8 > gold3
$ cat knbc1.utf8 knbc2.utf8 knbc3.utf8> corpus4
$ cat knbc4.utf8 > gold4

$ for i in `seq 1 1 4`;
> do
>   mkdir -p model/knbc$i && rm -rf model/knbc$i/*
>   jdepp -t 0 -I 1 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.0008  -i 40 -p
>   jdepp -t 3 -I 1 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.0008  -i 40 -p -- -s 0.02 -i 5 -t 1;
>   jdepp -t 0 -I 2 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.00005 -i 40 -p
>   jdepp -t 3 -I 2 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.00005 -i 40 -p -- -- -s 0.005 -i 5 -t 1;
>   cat gold$i | python ../tools/to_sent.py | mecab -d /usr/local/lib/mecab/dic/jumandic | jdepp -m model/knbc$i > result$i
> done

$ for i in `seq 1 1 4`;
> do
>   python ../tools/eval.py result$i gold$i 2> eval$i;
> done

$ cat eval[1-4] | python ../tools/eval_all.py 
chunk:
  precision: 0.9304 (25918/27856)
  recall:    0.9344 (25918/27737)
  f1:        0.9324
  sent acc.: 0.7510 (3143/4185)
depnd:
  precision: 0.7843 (18564/23671)
  recall:    0.7882 (18564/23552)
  f1:        0.7862
  sent acc.: 0.4937 (2066/4185)

これを見るとやはりCaboChaの結果が怪しい、、、なにかやらかしてる感じか:-)
念のため、J.DepPをCaboChaと同じSVMで学習させたらどうなるか見てみる。
#そして、ちゃんと計測してないけどJ.DepPの学習明らかに速いな。学習/解析速度ももう少しデータ用意して比べてみたい。
TinySVMをインストール。

$ brew install tinysvm

以下、学習部分のみ。
SVM(-l 1)を使うときのオプションがよくわかってないが、とりあえずモデルディレクトリにSVMLight形式の学習データができてたからTinySVMにそいつを学習させた。結構時間がかかる。。。

 :
[省略]
 :

$ for i in `seq 1 1 4`;
> do
>   mkdir -p model/knbc$i && rm -rf model/knbc$i/*
>   jdepp -t 0 -I 1 -c corpus$i -m model/knbc$i -- -t 0
>   svm_learn -t 1 -d 2 -c 1 model/knbc$i/chunk.train model/knbc$i/chunk
>   jdepp -t 3 -I 1 -c corpus$i -m model/knbc$i -- -t 0 -- -s 0.02 -i 5 -t 1;
>   jdepp -t 0 -I 2 -c corpus$i -m model/knbc$i -- -t 0
>   svm_learn -t 1 -d 2 -c 1 model/knbc$i/depnd.p0.train model/knbc$i/depnd.p0
>   jdepp -t 3 -I 2 -c corpus$i -m model/knbc$i -- -t 0 -- -- -s 0.005 -i 5 -t 1;
>   cat gold$i | python ../tools/to_sent.py | mecab -d /usr/local/lib/mecab/dic/jumandic | jdepp -m model/knbc$i > result$i
> done

$ for i in `seq 1 1 4`;
> do
>   python ../tools/eval.py result$i gold$i 2> eval$i;
> done

$ cat eval[1-4] | python ../tools/eval_all.py 
chunk:
  precision: 0.9308 (25893/27818)
  recall:    0.9335 (25893/27737)
  f1:        0.9322
  sent acc.: 0.7513 (3144/4185)
depnd:
  precision: 0.7809 (18455/23633)
  recall:    0.7836 (18455/23552)
  f1:        0.7822
  sent acc.: 0.4875 (2040/4185)

やはり、こっちはそれっぽい精度がでてる。。。にゃぜだ

ん〜、気になって眠れないけど眠いのでねる。

つづく(かも)

Python requests SSLError: EOF occurred in violation of protocol

とあるs付きのAPIにリクエストを投げた時に発生したエラー_φ(・_・

  • python 2.7.10
  • opelssl 1.0.1e
  • requests 2.8.1

In [21]: r = requests.get(url)
---------------------------------------------------------------------------
SSLError                                  Traceback (most recent call last)
<ipython-input-21-0aec23ab0de0> in <module>()
----> 1 r = requests.get(url)

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/api.pyc in get(url, params, **kwargs)
     67 
     68     kwargs.setdefault('allow_redirects', True)
---> 69     return request('get', url, params=params, **kwargs)
     70 
     71 

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/api.pyc in request(method, url, **kwargs)
     48 
     49     session = sessions.Session()
---> 50     response = session.request(method=method, url=url, **kwargs)
     51     # By explicitly closing the session, we avoid leaving sockets open which
     52     # can trigger a ResourceWarning in some cases, and look like a memory leak

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/sessions.pyc in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    466         }
    467         send_kwargs.update(settings)
--> 468         resp = self.send(prep, **send_kwargs)
    469 
    470         return resp

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/sessions.pyc in send(self, request, **kwargs)
    574 
    575         # Send the request
--> 576         r = adapter.send(request, **kwargs)
    577 
    578         # Total elapsed time of the request (approximately)

/root/.pyenv/versions/2.7.10/envs/pydata_env_2.7.10/lib/python2.7/site-packages/requests/adapters.pyc in send(self, request, stream, timeout, verify, cert, proxies)
    431         except (_SSLError, _HTTPError) as e:
    432             if isinstance(e, _SSLError):
--> 433                 raise SSLError(e, request=request)
    434             elif isinstance(e, ReadTimeoutError):
    435                 raise ReadTimeout(e, request=request)

SSLError: EOF occurred in violation of protocol (_ssl.c:590)

使われるSSLのバージョンの問題らしい。
requestsで使われるのは基底となるurllib3の実装によるようなので、特定のバージョンを使いたい場合は、下記のようにHTTPAdapterのサブクラスを作って、ssl_versionを明示するようにするとのこと。
今回は、ssl_version=ssl.PROTOCOL_TLSv1 とすることでうまくいった。

In [28]: from requests.adapters import HTTPAdapter

In [29]: from requests.packages.urllib3.poolmanager import PoolManager

In [30]: import ssl

In [31]: class MyAdapter(HTTPAdapter):
   ....:    def init_poolmanager(self, connections, maxsize, block=False):
   ....:        self.poolmanager = PoolManager(num_pools=connections,
   ....:                                       maxsize=maxsize,
   ....:                                       block=block,
   ....:                                       ssl_version=ssl.PROTOCOL_TLSv1)
   ....:         

In [32]: import requests

In [33]: s = requests.Session()

In [34]: s.mount('https://', MyAdapter())

In [35]: r = s.get(url)

MeCabのドメイン適応(再学習)

MeCabドメイン適応(再学習)すぐ忘れるので、他にも記事たくさんあるけど、なんとなく備忘メモ(手順だけ)。
MeCab: オリジナル辞書/コーパスからのパラメータ推定

まず、MeCabGoogle Project Hostingから消えてた。。。今はGooglドライブからもろもろダウンロード可能。
https://drive.google.com/folderview?id=0B4y35FiV1wh7fjQ5SkJETEJEYzlqcUY4WUlpZmR4dDlJMWI5ZUlXN2xZN2s2b0pqT3hMbTQ&usp=drive_web#list

準備するもの

  • MeCab本体:mecab-0.996.tar.gz
  • 辞書(IPA):mecab-ipadic-2.7.0-20070801.tar.gz
  • パラメータ推定モデル(IPA):mecab-ipadic-2.7.0-20070801.model.bz2

本体とIPA辞書は、下記の通りインストール済み

$ curl -O https://mecab.googlecode.com/files/mecab-0.996.tar.gz
$ tar xzf mecab-0.996.tar.gz
$ cd mecab-0.996
$ ./configure
$ make
$ make check
$ sudo make install
$ curl -O https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz
$ tar xzf mecab-ipadic-2.7.0-20070801.tar.gz
$ cd mecab-ipadic-2.7.0-20070801
$ ./configure --with-charset=utf8
$ make
$ sudo make install

サンプル文(形態素解析の誤り例)

以下の2文のうち、とりあえず明らかに誤りとわかる「外国人参政権」と「学校へ行こう!」を単語の追加とモデルの再学習で直してみます。

$ echo "外国人参政権は世界的には一般的でないものの、欧州地域など一定の制約下で認められているケースもある。" | mecab
外国	名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人参	名詞,一般,*,*,*,*,人参,ニンジン,ニンジン
政権	名詞,一般,*,*,*,*,政権,セイケン,セイケン
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
世界	名詞,一般,*,*,*,*,世界,セカイ,セカイ
的	名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
一般	名詞,一般,*,*,*,*,一般,イッパン,イッパン
的	名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
で	助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ない	助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
ものの	助詞,接続助詞,*,*,*,*,ものの,モノノ,モノノ
、	記号,読点,*,*,*,*,、,、,、
欧州	名詞,固有名詞,地域,一般,*,*,欧州,オウシュウ,オーシュー
地域	名詞,一般,*,*,*,*,地域,チイキ,チイキ
など	助詞,副助詞,*,*,*,*,など,ナド,ナド
一定	名詞,サ変接続,*,*,*,*,一定,イッテイ,イッテイ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
制約	名詞,サ変接続,*,*,*,*,制約,セイヤク,セイヤク
下	名詞,接尾,一般,*,*,*,下,カ,カ
で	助詞,格助詞,一般,*,*,*,で,デ,デ
認め	動詞,自立,*,*,一段,未然形,認める,ミトメ,ミトメ
られ	動詞,接尾,*,*,一段,連用形,られる,ラレ,ラレ
て	助詞,接続助詞,*,*,*,*,て,テ,テ
いる	動詞,非自立,*,*,一段,基本形,いる,イル,イル
ケース	名詞,一般,*,*,*,*,ケース,ケース,ケース
も	助詞,係助詞,*,*,*,*,も,モ,モ
ある	動詞,自立,*,*,五段・ラ行,基本形,ある,アル,アル
。	記号,句点,*,*,*,*,。,。,。
EOS
$ echo "TBSの伝説の番組『学校へ行こう!』が、この秋、一夜限り復活することが決まった。" | mecab
TBS	名詞,固有名詞,組織,*,*,*,*
の	助詞,連体化,*,*,*,*,の,ノ,ノ
伝説	名詞,一般,*,*,*,*,伝説,デンセツ,デンセツ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
番組	名詞,一般,*,*,*,*,番組,バングミ,バングミ
『	記号,括弧開,*,*,*,*,『,『,『
学校	名詞,一般,*,*,*,*,学校,ガッコウ,ガッコー
へ	助詞,格助詞,一般,*,*,*,へ,ヘ,エ
行こ	動詞,自立,*,*,五段・カ行促音便,未然ウ接続,行く,イコ,イコ
う	助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
!	記号,一般,*,*,*,*,!,!,!
』	記号,括弧閉,*,*,*,*,』,』,』
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
、	記号,読点,*,*,*,*,、,、,、
この	連体詞,*,*,*,*,*,この,コノ,コノ
秋	名詞,一般,*,*,*,*,秋,アキ,アキ
、	記号,読点,*,*,*,*,、,、,、
一夜	名詞,副詞可能,*,*,*,*,一夜,イチヤ,イチヤ
限り	名詞,非自立,副詞可能,*,*,*,限り,カギリ,カギリ
復活	名詞,サ変接続,*,*,*,*,復活,フッカツ,フッカツ
する	動詞,自立,*,*,サ変・スル,基本形,する,スル,スル
こと	名詞,非自立,一般,*,*,*,こと,コト,コト
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
決まっ	動詞,自立,*,*,五段・ラ行,連用タ接続,決まる,キマッ,キマッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

辞書とモデルの下準備

辞書、モデルともに文字コードEUC-JPなのでUTF-8に変換します。

$ tar xzf mecab-ipadic-2.7.0-20070801.tar.gz
$ nkf --overwrite -Ew mecab-ipadic-2.7.0-20070801/*
$ bzip2 -d mecab-ipadic-2.7.0-20070801.model.bz2
$ nkf --overwrite -Ew mecab-ipadic-2.7.0-20070801.model

設定ファイルとモデルファイルのcharsetの記述があるのでutf-8に変更しておきます。
mecab-ipadic-2.7.0-20070801/dicrc

 :
config-charset = UTF-8
 :
; yomi

mecab-ipadic-2.7.0-20070801.model

 :
charset: utf-8

-0.0001744043301648 B00:BOS/EOS/その他
 :

追加する単語のコスト推定をさせるために、バイナリ辞書を作成しておきます。(インストールしちゃう場合は、sudo make install もする)

$ cd mecab-ipadic-2.7.0-20070801
$ /usr/local/libexec/mecab/mecab-dict-index -f utf-8 -t utf-8

追加する単語をCSVで用意

追加する単語をCSVで用意します。

$ cat seed_with_empty_cost.csv
外国人参政権,,,,名詞,一般,*,*,*,*,外国人参政権,ガイコクジンサンセイケン,ガイコクジンサンセイケン
学校へ行こう!,,,,名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー

並びは、

表層形,左文脈ID,右文脈ID,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音

のようになってます。
#活用がある語は、活用を一行ずつ展開する必要あり。
今回は、始め試しに左文脈ID、右文脈ID、コストをオリジナルモデルに推定させてみるので空にしておきます。通常の再学習では「0,0,0」で問題無し。

コストの自動推定

オリジナルのモデルを使って、作成した追加単語CSVのコストを推定してもらいます。

$ /usr/local/libexec/mecab/mecab-dict-index -m mecab-ipadic-2.7.0-20070801.model -d mecab-ipadic-2.7.0-20070801 -f utf-8 -t utf-8 -a seed_with_empty_cost.csv -u seed.csv
$ cat seed.csv 
外国人参政権,1285,1285,5078,名詞,一般,*,*,*,*,外国人参政権,ガイコクジンサンセイケン,ガイコクジンサンセイケン
学校へ行こう!,1285,1285,5078,名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー

バイナリ辞書の再構築

コスト推定された追加単語のCSVを含めて、バイナリ辞書を再構築します。

$ cp seed.csv mecab-ipadic-2.7.0-20070801
$ cd mecab-ipadic-2.7.0-20070801
$ /usr/local/libexec/mecab/mecab-dict-index -f utf-8 -t utf-8

ここまでで、再テスト

単語が追加されたバイナリ辞書で、サンプル文を解析してみます。

$ echo "外国人参政権は世界的には一般的でないものの、欧州地域など一定の制約下で認められているケースもある。" | mecab -d .
外国人参政権	名詞,一般,*,*,*,*,外国人参政権,ガイコクジンサンセイケン,ガイコクジンサンセイケン
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
世界	名詞,一般,*,*,*,*,世界,セカイ,セカイ
的	名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
一般	名詞,一般,*,*,*,*,一般,イッパン,イッパン
的	名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
で	助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ない	助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
ものの	助詞,接続助詞,*,*,*,*,ものの,モノノ,モノノ
、	記号,読点,*,*,*,*,、,、,、
欧州	名詞,固有名詞,地域,一般,*,*,欧州,オウシュウ,オーシュー
地域	名詞,一般,*,*,*,*,地域,チイキ,チイキ
など	助詞,副助詞,*,*,*,*,など,ナド,ナド
一定	名詞,サ変接続,*,*,*,*,一定,イッテイ,イッテイ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
制約	名詞,サ変接続,*,*,*,*,制約,セイヤク,セイヤク
下	名詞,接尾,一般,*,*,*,下,カ,カ
で	助詞,格助詞,一般,*,*,*,で,デ,デ
認め	動詞,自立,*,*,一段,未然形,認める,ミトメ,ミトメ
られ	動詞,接尾,*,*,一段,連用形,られる,ラレ,ラレ
て	助詞,接続助詞,*,*,*,*,て,テ,テ
いる	動詞,非自立,*,*,一段,基本形,いる,イル,イル
ケース	名詞,一般,*,*,*,*,ケース,ケース,ケース
も	助詞,係助詞,*,*,*,*,も,モ,モ
ある	動詞,自立,*,*,五段・ラ行,基本形,ある,アル,アル
。	記号,句点,*,*,*,*,。,。,。
EOS
$ echo "TBSの伝説の番組『学校へ行こう!』が、この秋、一夜限り復活することが決まった。" | mecab -d .
TBS	名詞,固有名詞,組織,*,*,*,*
の	助詞,連体化,*,*,*,*,の,ノ,ノ
伝説	名詞,一般,*,*,*,*,伝説,デンセツ,デンセツ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
番組	名詞,一般,*,*,*,*,番組,バングミ,バングミ
『	記号,括弧開,*,*,*,*,『,『,『
学校	名詞,一般,*,*,*,*,学校,ガッコウ,ガッコー
へ	助詞,格助詞,一般,*,*,*,へ,ヘ,エ
行こ	動詞,自立,*,*,五段・カ行促音便,未然ウ接続,行く,イコ,イコ
う	助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
!	記号,一般,*,*,*,*,!,!,!
』	記号,括弧閉,*,*,*,*,』,』,』
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
、	記号,読点,*,*,*,*,、,、,、
この	連体詞,*,*,*,*,*,この,コノ,コノ
秋	名詞,一般,*,*,*,*,秋,アキ,アキ
、	記号,読点,*,*,*,*,、,、,、
一夜	名詞,副詞可能,*,*,*,*,一夜,イチヤ,イチヤ
限り	名詞,非自立,副詞可能,*,*,*,限り,カギリ,カギリ
復活	名詞,サ変接続,*,*,*,*,復活,フッカツ,フッカツ
する	動詞,自立,*,*,サ変・スル,基本形,する,スル,スル
こと	名詞,非自立,一般,*,*,*,こと,コト,コト
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
決まっ	動詞,自立,*,*,五段・ラ行,連用タ接続,決まる,キマッ,キマッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

外国人参政権」は直ったけど、「学校へ行こう!」は変化なし。。。もう一手間かけます。

再学習用コーパスの準備

モデルを再学習させるために、コーパス(正しい解析結果)を用意します。フォーマットは、MeCabのデフォルトの出力と同じです。

$ cat corpus 
TBS	名詞,固有名詞,組織,*,*,*,*
の	助詞,連体化,*,*,*,*,の,ノ,ノ
伝説	名詞,一般,*,*,*,*,伝説,デンセツ,デンセツ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
番組	名詞,一般,*,*,*,*,番組,バングミ,バングミ
『	記号,括弧開,*,*,*,*,『,『,『
学校へ行こう!	名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
』	記号,括弧閉,*,*,*,*,』,』,』
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
、	記号,読点,*,*,*,*,、,、,、
この	連体詞,*,*,*,*,*,この,コノ,コノ
秋	名詞,一般,*,*,*,*,秋,アキ,アキ
、	記号,読点,*,*,*,*,、,、,、
一夜	名詞,副詞可能,*,*,*,*,一夜,イチヤ,イチヤ
限り	名詞,非自立,副詞可能,*,*,*,限り,カギリ,カギリ
復活	名詞,サ変接続,*,*,*,*,復活,フッカツ,フッカツ
する	動詞,自立,*,*,サ変・スル,基本形,する,スル,スル
こと	名詞,非自立,一般,*,*,*,こと,コト,コト
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
決まっ	動詞,自立,*,*,五段・ラ行,連用タ接続,決まる,キマッ,キマッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

モデル(CRF パラメータ)の再学習

コーパスを使って、モデルを更新します。

$ /usr/local/libexec/mecab/mecab-cost-train -d mecab-ipadic-2.7.0-20070801 -M mecab-ipadic-2.7.0-20070801.model -c 1.0 corpus mecab-ipadic-2.7.0-20070801_custom.model
配布用辞書の作成

更新したモデルで配布用辞書を作成します。-o で元の辞書とは別の辞書として出力しています。

$ mkdir mecab-ipadic-2.7.0-20070801_custom
$ /usr/local/libexec/mecab/mecab-dict-gen -d mecab-ipadic-2.7.0-20070801 -o mecab-ipadic-2.7.0-20070801_custom -m mecab-ipadic-2.7.0-20070801_custom.model

コストが変わりました。

$ cat mecab-ipadic-2.7.0-20070801_custom/seed.csv 
外国人参政権,1135,1135,5176,名詞,一般,*,*,*,*,外国人参政権,ガイコクジンサンセイケン,ガイコクジンサンセイケン
学校へ行こう!,1135,1135,5087,名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
バイナリ辞書の再構築

この状態で、もう一回辞書を再構築すれば出来上がり。

$ cd mecab-ipadic-2.7.0-20070801_custom
$ /usr/local/libexec/mecab/mecab-dict-index -f utf-8 -t utf-8
もっかい、テスト

ビルドした辞書で『学校へ行こう!』を再テストしてみます。

$ echo "TBSの伝説の番組『学校へ行こう!』が、この秋、一夜限り復活することが決まった。" | mecab -d .
TBS	名詞,固有名詞,組織,*,*,*,*
の	助詞,連体化,*,*,*,*,の,ノ,ノ
伝説	名詞,一般,*,*,*,*,伝説,デンセツ,デンセツ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
番組	名詞,一般,*,*,*,*,番組,バングミ,バングミ
『	記号,括弧開,*,*,*,*,『,『,『
学校へ行こう!	名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
』	記号,括弧閉,*,*,*,*,』,』,』
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
、	記号,読点,*,*,*,*,、,、,、
この	連体詞,*,*,*,*,*,この,コノ,コノ
秋	名詞,一般,*,*,*,*,秋,アキ,アキ
、	記号,読点,*,*,*,*,、,、,、
一夜	名詞,副詞可能,*,*,*,*,一夜,イチヤ,イチヤ
限り	名詞,非自立,副詞可能,*,*,*,限り,カギリ,カギリ
復活	名詞,サ変接続,*,*,*,*,復活,フッカツ,フッカツ
する	動詞,自立,*,*,サ変・スル,基本形,する,スル,スル
こと	名詞,非自立,一般,*,*,*,こと,コト,コト
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
決まっ	動詞,自立,*,*,五段・ラ行,連用タ接続,決まる,キマッ,キマッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS
$ echo "昨日の『学校へ行こう!』は、面白かった。" | mecab -d .
昨日	名詞,副詞可能,*,*,*,*,昨日,キノウ,キノー
の	助詞,連体化,*,*,*,*,の,ノ,ノ
『	記号,括弧開,*,*,*,*,『,『,『
学校へ行こう!	名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
』	記号,括弧閉,*,*,*,*,』,』,』
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
、	記号,読点,*,*,*,*,、,、,、
面白かっ	形容詞,自立,*,*,形容詞・アウオ段,連用タ接続,面白い,オモシロカッ,オモシロカッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS
$ echo "昨日の「学校へ行こう!」は、面白かった。" | mecab -d .
昨日	名詞,副詞可能,*,*,*,*,昨日,キノウ,キノー
の	助詞,連体化,*,*,*,*,の,ノ,ノ
「	記号,括弧開,*,*,*,*,「,「,「
学校へ行こう!	名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
」	記号,括弧閉,*,*,*,*,」,」,」
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
、	記号,読点,*,*,*,*,、,、,、
面白かっ	形容詞,自立,*,*,形容詞・アウオ段,連用タ接続,面白い,オモシロカッ,オモシロカッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS
$ echo "昨日の学校へ行こう!は、面白かった。" | mecab -d .
昨日	名詞,副詞可能,*,*,*,*,昨日,キノウ,キノー
の	助詞,連体化,*,*,*,*,の,ノ,ノ
学校へ行こう!	名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
、	記号,読点,*,*,*,*,、,、,、
面白かっ	形容詞,自立,*,*,形容詞・アウオ段,連用タ接続,面白い,オモシロカッ,オモシロカッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS
$ echo "明日こそ、学校へ行こう!" | mecab -d .
明日	名詞,副詞可能,*,*,*,*,明日,アシタ,アシタ
こそ	助詞,係助詞,*,*,*,*,こそ,コソ,コソ
、	記号,読点,*,*,*,*,、,、,、
学校	名詞,一般,*,*,*,*,学校,ガッコウ,ガッコー
へ	助詞,格助詞,一般,*,*,*,へ,ヘ,エ
行こ	動詞,自立,*,*,五段・カ行促音便,未然ウ接続,行く,イコ,イコ
う	助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
!	記号,一般,*,*,*,*,!,!,!
EOS
$ echo "明日こそ、学校へ行こう!を見よう!" | mecab -d .
明日	名詞,副詞可能,*,*,*,*,明日,アシタ,アシタ
こそ	助詞,係助詞,*,*,*,*,こそ,コソ,コソ
、	記号,読点,*,*,*,*,、,、,、
学校へ行こう!	名詞,一般,*,*,*,*,学校へ行こう!,ガッコウヘイコウ,ガッコーエイコー
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
見よ	動詞,自立,*,*,一段,未然ウ接続,見る,ミヨ,ミヨ
う	助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
!	記号,一般,*,*,*,*,!,!,!
EOS

1サンプル学習させただけだけど、そこそこ正しく形態素として認識するようになってる気がする。

おしまい

とりあえず、手順だけ。
設定ファイルの細かいところとか、実務的なところに踏み込んだ内容は、もう少し勉強しないとダメだ。。。
#そして「学校へ行こう!」は、固有名詞だった。。。めんどいからそのまま

ApacheアクセスログをElasticsearchへ流す

Elasticsearchはdockerコンテナで用意、Apache側は落ちてたwordpressのコンテナにtd-agentをインストールしてテスト

  • td-agent 0.12.12
  • Elasticsearch 1.7.1
td-agent のインストール
curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh | sh
標準出力で確認

apache2のテンプレート↓がデフォルトで用意されているのでそのまま使う

format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

/etc/td-agent/td-agent.conf

<source>
  type tail
  format apache2
  path /var/log/apache2/access.log
  pos_file /var/log/td-agent/apache_access.pos
  tag apache.access
</source>

<match *.**>
  type copy
  <store>
    type stdout
  </store>
</match>

td-agent を起動

/etc/init.d/td-agent start

dockerコンテナの上なんでもrootで動かしてるせいでログが読めない。。。

2015-10-04 17:16:11 +0000 [error]: Permission denied @ rb_sysopen - /var/log/apache2/access.log
  2015-10-04 17:16:11 +0000 [error]: suppressed same stacktrace

適当対応
/etc/init.d/td-agent

 :
TD_AGENT_USER=root
TD_AGENT_GROUP=root
 :

こんなん出た

2015-10-06 22:25:17 +0900 raw.apache.access: {"host":"192.168.1.1","user":null,"method":"POST","path":"/wp-admin/admin-ajax.php","code":200,"size":580,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"}
2015-10-06 22:25:35 +0900 raw.apache.access: {"host":"192.168.1.1","user":null,"method":"GET","path":"/wp-admin/index.php","code":200,"size":14048,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"}
2015-10-06 22:25:55 +0900 raw.apache.access: {"host":"192.168.1.1","user":null,"method":"GET","path":"/wp-admin/index.php","code":200,"size":14046,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"}
UAを解析したい

tagomoris/fluent-plugin-woothee · GitHub

# td-agent-gem install fluent-plugin-woothee
WARN: Unresolved specs during Gem::Specification.reset:
      json (>= 1.4.3)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.
ERROR:  While executing gem ... (Gem::RemoteFetcher::FetchError)
    Errno::ECONNREFUSED: Connection refused - connect(2) for "your-dns-needs-immediate-attention.dev" port 443 (https://your-dns-needs-immediate-attention.dev/quick/Marshal.4.8/woothee-1.2.0.gemspec.rz)

変なエラーでた、コンテナをdevドメインにしてあるせいらしい
your-dns-needs-immediate-attention | Triple-networks

# echo "search home.local" >> /etc/resolv.conf 

/etc/td-agent/td-agent.conf

<source>
  type tail
  format apache2
  path /var/log/apache2/access.log
  pos_file /var/log/td-agent/apache_access.pos
  tag raw.apache.access
</source>

<match raw.**>
  type woothee
  key_name agent
  remove_prefix raw
  add_prefix parsed
  merge_agent_info yes
  out_key_name agent_name
  out_key_category agent_category
  out_key_os agent_os
  out_key_os_version agent_os_version
  out_key_version agent_version
  out_key_vendor agent_vendor
</match>

<match *.**>
  type copy
  <store>
    type stdout
  </store>
</match>

こうなった。

2015-10-06 23:17:10 +0900 parsed.apache.access: {"host":"192.168.1.1","user":null,"method":"GET","path":"/wp-admin/index.php","code":200,"size":14014,"referer":"http://192.168.1.10/wp-admin/plugins.php","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36","agent_name":"Chrome","agent_category":"pc","agent_os":"Mac OSX","agent_os_version":"10.10.5","agent_version":"45.0.2454.101","agent_vendor":"Google"}
2015-10-06 23:18:17 +0900 parsed.apache.access: {"host":"192.168.1.1","user":null,"method":"GET","path":"/wp-admin/index.php","code":200,"size":14051,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3","agent_name":"Safari","agent_category":"smartphone","agent_os":"iPhone","agent_os_version":"5.0","agent_version":"5.1","agent_vendor":"Apple"}
2015-10-06 23:19:11 +0900 parsed.apache.access: {"host":"192.168.1.1","user":null,"method":"GET","path":"/wp-admin/index.php","code":200,"size":14049,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30","agent_name":"Safari","agent_category":"smartphone","agent_os":"Android","agent_os_version":"4.0.4","agent_version":"4.0","agent_vendor":"Apple"}

他にも、filter_categories、drop_categories で特定のカテゴリを出力、破棄できる。
クローラリクエストを効率良くざっくり破棄した場合は、'woothee_fast_crawler_filter' を使う、完璧に破棄したい場合は、'woothee' + 'drop_categories crawler'を併せて使う。

geoipも使ってみる

y-ken/fluent-plugin-geoip · GitHub

# apt-get install build-essential
# apt-get install libgeoip-dev
# td-agent-gem install fluent-plugin-geoip

デフォルトでバンドルされてる無償データベースだと国レベル(緯度経度も)
/etc/td-agent/td-agent.conf

<source>
  type tail
  format apache2
  path /var/log/apache2/access.log
  pos_file /var/log/td-agent/apache_access.pos
  tag raw.apache.access
</source>

<match raw.**>
  type woothee
  key_name agent
  remove_prefix raw
  add_prefix ua_parsed
  merge_agent_info yes
  out_key_name agent_name
  out_key_category agent_category
  out_key_os agent_os
  out_key_os_version agent_os_version
  out_key_version agent_version
  out_key_vendor agent_vendor
</match>

<match ua_parsed.**>
  type geoip

  # Specify one or more geoip lookup field which has ip address (default: host)
  # in the case of accessing nested value, delimit keys by dot like 'host.ip'.
  geoip_lookup_key  host

  # Specify optional geoip database (using bundled GeoLiteCity databse by default)
  #geoip_database    "/path/to/your/GeoIPCity.dat"

  #enable_key_country_code geoip_country

  # Set adding field with placeholder (more than one settings are required.)
  <record>
    #city            ${city["host"]}
    geoip_latitude        ${latitude["host"]}
    geoip_longitude       ${longitude["host"]}
    geoip_country_code3   ${country_code3["host"]}
    geoip_country         ${country_code["host"]}
    country_name    ${country_name["host"]}
    #dma             ${dma_code["host"]}
    #area            ${area_code["host"]}
    #region          ${region["host"]}
    geoip_location_properties  '{ "lat" : ${latitude["host"]}, "lon" : ${longitude["host"]} }'
    geoip_location_string      ${latitude["host"]},${longitude["host"]}
    geoip_location_array       '[${longitude["host"]},${latitude["host"]}]'
  </record>

  # Settings for tag
  remove_tag_prefix ua_parsed.
  tag               parsed.${tag}

  # To avoid get stacktrace error with `[null, null]` array for elasticsearch.
  skip_adding_null_record  true

  # Set log_level for fluentd-v0.10.43 or earlier (default: warn)
  log_level         info

  # Set buffering time (default: 0s)
  flush_interval    1s
</match>

<match *.**>
  type copy
  <store>
    type stdout
  </store>
</match>

geoip_locationはどの形式でもO.K. 適当なIPを流して確認

2015-10-07 01:35:08 +0900 geoip.apache.access: {"host":"1.0.0.0","user":null,"method":"POST","path":"/wp-admin/admin-ajax.php","code":200,"size":580,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36","agent_name":"Chrome","agent_category":"pc","agent_os":"Mac OSX","agent_os_version":"10.10.5","agent_version":"45.0.2454.101","agent_vendor":"Google","geoip_latitude":-27.0,"geoip_longitude":133.0,"geoip_country_code3":"AUS","geoip_country":"AU","country_name":"Australia","geoip_location_properties":{"lat":-27.0,"lon":133.0},"geoip_location_string":"-27.0,133.0","geoip_location_array":[133.0,-27.0]}
2015-10-07 01:36:08 +0900 geoip.apache.access: {"host":"128.0.0.0","user":null,"method":"POST","path":"/wp-admin/admin-ajax.php","code":200,"size":580,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36","agent_name":"Chrome","agent_category":"pc","agent_os":"Mac OSX","agent_os_version":"10.10.5","agent_version":"45.0.2454.101","agent_vendor":"Google","geoip_latitude":46.0,"geoip_longitude":25.0,"geoip_country_code3":"ROU","geoip_country":"RO","country_name":"Romania","geoip_location_properties":{"lat":46.0,"lon":25.0},"geoip_location_string":"46.0,25.0","geoip_location_array":[25.0,46.0]}
2015-10-07 01:37:08 +0900 geoip.apache.access: {"host":"114.170.237.217","user":null,"method":"POST","path":"/wp-admin/admin-ajax.php","code":200,"size":580,"referer":"http://192.168.1.10/wp-admin/index.php","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36","agent_name":"Chrome","agent_category":"pc","agent_os":"Mac OSX","agent_os_version":"10.10.5","agent_version":"45.0.2454.101","agent_vendor":"Google","geoip_latitude":35.689998626708984,"geoip_longitude":139.69000244140625,"geoip_country_code3":"JPN","geoip_country":"JP","country_name":"Japan","geoip_location_properties":{"lat":35.689998626708984,"lon":139.69000244140625},"geoip_location_string":"35.689998626708984,139.69000244140625","geoip_location_array":[139.69000244140625,35.689998626708984]}
Elasticsearchへ流す

uken/fluent-plugin-elasticsearch · GitHub

# td-agent-gem install fluent-plugin-elasticsearch

/etc/td-agent/td-agent.conf

 :
<match parsed.**>
  type elasticsearch
  hosts es1.containers.dev:9200,es2.containers.dev:9200
  type_name access
  logstash_format true
  logstash_prefix apache_log_wordpress
  logstash_dateformat %Y.%m
  flush_interval 10s
</match>

geoipの緯度経度がそのままだとgeo_pointにマッピングされないので、明示的にタイプをマッピングしておく。

curl -XPUT 'es1.containers.dev:9200/_template/apache_log/?pretty' -d '
{
  "template": "apache_log*",
  "mappings": {
    "access": {
      "properties": {
        "geoip_location_properties": {
          "type": "geo_point"
        },
        "geoip_location_string": {
          "type": "geo_point"
        },
        "geoip_location_array": {
          "type": "geo_point"
        }
      }
    }
  }
}
'

適当にログを流す、ドキュメントはこんな感じになった

{
  "_index": "apache_log_wordpress-2015.10",
  "_type": "access",
  "_id": "AVBOD5OpAEC4whOefbLc",
  "_score": 1,
  "_source": {
    "host": "1.0.0.0",
    "user": null,
    "method": "POST",
    "path": "/wp-admin/admin-ajax.php",
    "code": 200,
    "size": 580,
    "referer": "http://192.168.1.10/wp-admin/index.php",
    "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
    "agent_name": "Chrome",
    "agent_category": "pc",
    "agent_os": "Mac OSX",
    "agent_os_version": "10.10.5",
    "agent_version": "45.0.2454.101",
    "agent_vendor": "Google",
    "geoip_latitude": -27,
    "geoip_longitude": 133,
    "geoip_country_code3": "AUS",
    "geoip_country": "AU",
    "country_name": "Australia",
    "geoip_location_properties": {
      "lat": -27,
      "lon": 133
      },
    "geoip_location_string": "-27.0,133.0",
    "geoip_location_array": [133, -27],
    "@timestamp": "2015-10-07T01:35:08+09:00"
  }
}

マッピングも確認

# curl -XGET 'es1.containers.dev:9200/apache_log_wordpress-2015.10/_mapping/?pretty'
{
  "apache_log_wordpress-2015.10" : {
    "mappings" : {
      "access" : {
        "properties" : {
          "@timestamp" : {
            "type" : "date",
            "format" : "dateOptionalTime"
          },
          "agent" : {
            "type" : "string"
          },
          "agent_category" : {
            "type" : "string"
          },
          "agent_name" : {
            "type" : "string"
          },
          "agent_os" : {
            "type" : "string"
          },
          "agent_os_version" : {
            "type" : "string"
          },
          "agent_vendor" : {
            "type" : "string"
          },
          "agent_version" : {
            "type" : "string"
          },
          "code" : {
            "type" : "long"
          },
          "country_name" : {
            "type" : "string"
          },
          "geoip_country" : {
            "type" : "string"
          },
          "geoip_country_code3" : {
            "type" : "string"
          },
          "geoip_latitude" : {
            "type" : "double"
          },
          "geoip_location_array" : {
            "type" : "geo_point"
          },
          "geoip_location_properties" : {
            "type" : "geo_point"
          },
          "geoip_location_string" : {
            "type" : "geo_point"
          },
          "geoip_longitude" : {
            "type" : "double"
          },
          "host" : {
            "type" : "string"
          },
          "method" : {
            "type" : "string"
          },
          "path" : {
            "type" : "string"
          },
          "referer" : {
            "type" : "string"
          },
          "size" : {
            "type" : "long"
          }
        }
      }
    }
  }
}
ここまで

Kibanaで軽く確認してチャートや地図も問題無し、@timestampもちゃんとログのリクエスト時間になってる。
とりあえず導入としてはこんなもんで。

RDBのデータをELKで集計・可視化する

たまにくる「これの件数教えて」とか「Kibana見て」にしたい

とりあえずとっかかりとして、MySQLからElasticsearchにデータもってって、Aggregationを触ってみる

  • MySQL入力からElasticsearch出力とか
  • 基本的なSQLの集計クエリをどう置き換えるかとか

あたりの備忘録

  • Elasticssearch 1.7.1
  • Logstash 1.5.4

適当なデータを用意する

特定の場所(バッセンとか)とそのレビューのデータ

  • spot: ○スローバッティングセンター とか
  • location: 荻窪○スローバッティングセンター とか、locationには、緯度経度が、"lat,lon" 形式(get_pointとしてそのまま扱える形)で入っている
  • review: 星3つ とか

mysql> desc spot;
+-----------------+-------------+------+-----+---------------------+-----------------------------+
| Field           | Type        | Null | Key | Default             | Extra                       |
+-----------------+-------------+------+-----+---------------------+-----------------------------+
| sid             | int(11)     | NO   | PRI | NULL                | auto_increment              |
| name            | varchar(50) | NO   |     | NULL                |                             |
| regist_datetime | datetime    | NO   |     | 0000-00-00 00:00:00 |                             |
| update_datetime | datetime    | NO   |     | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
+-----------------+-------------+------+-----+---------------------+-----------------------------+
4 rows in set (0.00 sec)

mysql> desc location;
+-----------------+-------------+------+-----+---------------------+-----------------------------+
| Field           | Type        | Null | Key | Default             | Extra                       |
+-----------------+-------------+------+-----+---------------------+-----------------------------+
| lid             | int(11)     | NO   | PRI | NULL                | auto_increment              |
| sid             | int(11)     | NO   |     | NULL                |                             |
| pref            | varchar(4)  | NO   |     | NULL                |                             |
| location        | varchar(30) | NO   |     | NULL                |                             |
| regist_datetime | datetime    | NO   |     | 0000-00-00 00:00:00 |                             |
| update_datetime | datetime    | NO   |     | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
+-----------------+-------------+------+-----+---------------------+-----------------------------+
6 rows in set (0.01 sec)

mysql> desc review;
+-----------------+--------------+------+-----+---------------------+-----------------------------+
| Field           | Type         | Null | Key | Default             | Extra                       |
+-----------------+--------------+------+-----+---------------------+-----------------------------+
| rid             | int(11)      | NO   | PRI | NULL                | auto_increment              |
| lid             | int(11)      | NO   |     | NULL                |                             |
| nickname        | varchar(10)  | NO   |     | NULL                |                             |
| rate            | decimal(2,1) | NO   |     | 0.0                 |                             |
| regist_datetime | datetime     | NO   |     | 0000-00-00 00:00:00 |                             |
| update_datetime | datetime     | NO   |     | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
+-----------------+--------------+------+-----+---------------------+-----------------------------+
6 rows in set (0.00 sec)

マッピング

{
    "settings" : {
        "number_of_shards" : 2,
        "number_of_replica" : 2,
        "analysis": {
            "analyzer": {
                "custom_bigram": {
                    "tokenizer": "custom_bigram_tokenizer"
                }
            },
            "tokenizer": {
                "custom_bigram_tokenizer": {
                    "type": "nGram",
                    "min_gram": "2",
                    "max_gram": "2",
                    "token_chars": [
                        "letter",
                        "digit"
                    ]
                }
            }
        }
    },
    "mappings" : {
        "spot" : {
            "properties" : {
                "name" : {
                    "type" : "string",
                    "index" : "not_analyzed",
                    "fields": {
                        "bigram": {"type": "string", "index": "analyzed", "analyzer": "custom_bigram" }
                    }
                },
                "pref" : {
                    "type" : "string",
                    "index" : "not_analyzed"
                },
                "location": {
                    "type": "geo_point"
                },
                "regist_datetime" : {
                    "type" : "date"
                },
                "update_datetime" : {
                    "type" : "date"
                }
            }
        },
        "review": {
            "properties" : {
                "name" : {
                    "type" : "string",
                    "index" : "not_analyzed",
                    "fields": {
                        "bigram": {"type": "string", "index": "analyzed", "analyzer": "custom_bigram" }
                    }
                },
                "pref" : {
                    "type" : "string",
                    "index" : "not_analyzed"
                },
                "lid": {
                    "type": "integer"
                },
                "location": {
                    "type": "geo_point"
                },
                "nickname": {
                    "type": "string"
                },
                "rate": {
                    "type": "float"
                },
                "regist_datetime" : {
                    "type" : "date"
                }
            }
        }
    }
}

MySQL to Elasticsearch

RDBからの入力は、jdbcプラグインがあるので今回はそれを使う。

bin/plugin install logstash-input-jdbc

jdbc_es_pipeline_spot.conf

input {
    jdbc {
        jdbc_driver_library => "./mysql-connector-java-5.1.34.jar"
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/elk"
        jdbc_user => "root"
        jdbc_password => ""
        statement => "
            SELECT
                l.lid `_id`
            ,   s.name
            ,   l.pref
            ,   l.location
            ,   DATE_FORMAT(l.regist_datetime, '%Y-%m-%dT%H:%i:%s.000+0900') `regist_datetime`
            ,   DATE_FORMAT(l.update_datetime, '%Y-%m-%dT%H:%i:%s.000+0900') `update_datetime`
            FROM
                location l
                INNER JOIN spot s
                    ON s.sid = l.sid
            ORDER BY
                l.lid
        "
    }
}

filter {
    ruby {
        code => "localtime = event.timestamp.time.localtime"
    }
}

output {
    elasticsearch {
        protocol => "http"
        host => "192.168.1.10"
        port => "9200"
        index => "learning_elk"
        document_type => "spot"
        document_id => "%{_id}"
    }
}

jdbc_es_pipeline_review.conf

input {
    jdbc {
        jdbc_driver_library => "./mysql-connector-java-5.1.34.jar"
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/elk"
        jdbc_user => "root"
        jdbc_password => ""
        statement => "
            SELECT
                r.rid `_id`
            ,   s.name
            ,   l.pref
            ,   l.lid
            ,   l.location
            ,   r.nickname
            ,   r.rate
            ,   DATE_FORMAT(r.regist_datetime, '%Y-%m-%dT%H:%i:%s.000+0900') `regist_datetime`
            ,   DATE_FORMAT(r.update_datetime, '%Y-%m-%dT%H:%i:%s.000+0900') `update_datetime`
            FROM
                review r
                INNER JOIN location l
                    ON l.lid = r.lid
                INNER JOIN spot s
                    ON s.sid = l.sid
            ORDER BY
                r.rid
        "
    }
}

filter {
    ruby {
        code => "localtime = event.timestamp.time.localtime"
    }
}

output {
    elasticsearch {
        protocol => "http"
        host => "192.168.1.10"
        port => "9200"
        index => "learning_elk"
        document_type => "review"
        document_id => "%{_id}"
    }
}

なんか冗長だけどそのままそれぞれ実行

bin/logstash -f jdbc_es_pipeline_spot.conf
bin/logstash -f jdbc_es_pipeline_review.conf

集計操作

単純カウント

SQL

SELECT
	s.name
,	l.pref
,	COUNT(l.sid) `num_spot`
FROM
	location l
	INNER JOIN spot s
		ON s.sid = l.sid
GROUP BY
	s.name
,	l.pref
ORDER BY
	`num_spot` DESC

Aggregation
複数軸は常にネストさせる必要がある?階層的に集約とれるのはいいけど、親いらない時はtermsに2つ書きたい。。。

{
    "aggs": {
        "count_by_name": {
            "terms": {
                "field": "name",
                "order": { "_count" : "desc" }
            },
            "aggs": {
                "count_by_pref": {
                    "terms": {
                        "field": "pref",
                        "order": { "_count" : "desc" }
                    }
                }
            }
        }
    }
}
ユニーク数カウント

SQL

SELECT
	l.pref
,	COUNT(DISTINCT s.name) `num_spot_name`
FROM
	location l
	INNER JOIN spot s
		ON s.sid = l.sid
GROUP BY
	l.pref
ORDER BY
	`num_spot_name` DESC

Aggregation

{
    "aggs": {
        "count_by_pref": {
            "terms": {
                "field": "pref",
                "order": { "_count" : "desc" }
            },
            "aggs": {
                "count_unique_name": {
                    "cardinality": {
                        "field": "name"
                    }
                }
            }
        }
    }
}
基本統計量(カウント、最小、最大、平均、合計)

SQL

SELECT
	s.name
,	l.lid
,	COUNT(r.rid) `num_rate`
,	MIN(r.rate) `min_rate`
,	MAX(r.rate) `max_rate`
,	AVG(r.rate) `avg_rate`
,	SUM(r.rate) `total_rate`
FROM
	review r
	INNER JOIN location l
		ON l.lid = r.lid
	INNER JOIN spot s
		ON s.sid = l.sid
GROUP BY
	s.name
,	l.lid

Aggregation

{
    "aggs": {
        "group_by_name": {
            "terms": {
                "field": "name"
            },
            "aggs": {
                "stats_rate": {
                    "stats": {
                        "field": "rate"
                    }
                },
                "group_by_location": {
                    "terms": {
                        "field": "lid"
                    },
                    "aggs": {
                        "stats_rate": {
                            "stats": {
                                "field": "rate"
                            }
                        }
                    }
                }
            }
        }
    }
}
条件付き

SQL

SELECT
	s.name
,	COUNT(r.rid) `num_rate`
,	MIN(r.rate) `min_rate`
,	MAX(r.rate) `max_rate`
,	AVG(r.rate) `avg_rate`
,	SUM(r.rate) `total_rate`
,	COUNT(IF(r.nickname = 'mike', r.rid, NULL)) `mikes_num_rate`
,	MIN(IF(r.nickname = 'mike', r.rate, NULL)) `mikes_min_rate`
,	MAX(IF(r.nickname = 'mike', r.rate, NULL)) `mikes_max_rate`
,	AVG(IF(r.nickname = 'mike', r.rate, NULL)) `mikes_avg_rate`
,	SUM(IF(r.nickname = 'mike', r.rate, NULL)) `mikes_total_rate`
FROM
	review r
	INNER JOIN location l
		ON l.lid = r.lid
	INNER JOIN spot s
		ON s.sid = l.sid
GROUP BY
	s.name

Aggregation?

{
    "aggs": {
        "group_by_name": {
            "terms": {
                "field": "name"
            },
            "aggs": {
                "stats_rate": {
                    "stats": {
                        "field": "rate"
                    }
                }
            }
        },
        "if_mike": {
            "filter": {
                "term" : {
                    "nickname": "mike"
                }
            },
            "aggs": {
                "group_by_name": {
                    "terms": {
                        "field": "name"
                    },
                    "aggs": {
                        "stats_rate": {
                            "stats": {
                                "field": "rate"
                            }
                        }
                    }
                }
            }
        }
    }
}

とりあえずここまで

なんとなく、集計クエリとなると、大きなデータの日次なり月次なりの統計量を別のインデックスに保存して(INSERT ... SELECT ... 的な)推移とか見たくなりそうだけど、Aggregationだとさすがにそれは、Pythonとか書く必要があるか。
あと今回は、Logstash使ったけどRDBからのインポートは、他に
jprante/elasticsearch-jdbc · GitHub
embulk/embulk · GitHub
とかがつかえる。embulkの方がよかったかな。。。
#Kなし

とりあえず、ドキュメントを一通り読まねば。。。

おまけメモ

こないだ触ったデータが、緯度と経度をテーブルの別のカラムに複数もっててそれをElasticsearchのgeo_pointにマッピングする必要があったんだけど、SQL以外書きたくなかったので、zip関数(のようなもの)を作った

gist.github.com

mysql> select * from pivot;
+--------+
| rownum |
+--------+
|      1 |
|      2 |
|      3 |
|      4 |
|      5 |
+--------+

mysql> select zip('a b c', '1 2 3', ' ', ',');
+----------------------------------------+
| zip('a b c', '1 2 3', ' ', ',')        |
+----------------------------------------+
| a,1 b,2 c,3                            |
+----------------------------------------+
1 row in set (0.00 sec)