2017年9月18日月曜日

形態素解析の速度比較

日本語形態素解析の速度比較をしてみました。
環境は、仮想マシン上でXeon 2コアを割り当てた4GBのメモリとSSDのストレージを使った環境です。
解析対象はWikipediaExtractorで分割したWikipediaのデータ5MB分です。

正確性でいけばJUMAN++なんでしょうが、やっぱり速さとの関係上、大量のドキュメント処理するにはMeCabかなという心象です。あとで試験してみたところkuromojiもなかなかに早いです。
実行したコマンド 実行時間
chasen -iw 2.525s
mecab -Owakati 2.330s
mecab -Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd 2.435s
mecab -Ochasen 4.098s
mecab -Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd 3.887s
juman 30.607s
juman++ 10782.644s
kuromoji 4.862s
janome 716.016s
Janomeとkuromojiを加えました。
kuromojiはやい!


グラフ化してみるとこんな感じでした。(縦軸が対数になっているので注意してください。)

Janomeのテスト用コード

Janomeについては、テスト用に標準入力を受け取るようにコードを作ってパイプで繋げて計測しました。
ファイル: test_janome.py
import sys
from janome.tokenizer import Tokenizer

t = Tokenizer()
for line in sys.stdin:
    tokens = t.tokenize(line, wakati=True)
    print(" ".join(tokens))


kuromojiのテスト用コード

kuromojiについては、exampleディレクトリの下に、ちょうどいいサンプルがついてきますので、そのコードををほんの少し変更し、こちらもパイプで繋げて計測しました。
ファイル: TokenizerExample.java
package org.atilika.kuromoji.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import org.atilika.kuromoji.Token;
import org.atilika.kuromoji.Tokenizer;
import org.atilika.kuromoji.Tokenizer.Mode;

public class TokenizerExample {

        public static void main(String[] args) throws IOException {
                Tokenizer tokenizer;
                if (args.length == 1) {
                        Mode mode = Mode.valueOf(args[0].toUpperCase());
                        tokenizer = Tokenizer.builder().mode(mode).build();
                } else if (args.length == 2) {
                        Mode mode = Mode.valueOf(args[0].toUpperCase());
                        tokenizer = Tokenizer.builder().mode(mode).userDictionary(args[1]).build();
                } else {
                        tokenizer = Tokenizer.builder().build();
                }
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                String line;
                while ((line = reader.readLine()) != null) {
                        List result = tokenizer.tokenize(line);
                        for (Token token : result) {
                                System.out.print(token.getSurfaceForm() + " ");
                        }
                }
        }
}

2017年9月16日土曜日

WikipediaのデータをWord2Vecに

今更ですが、WikipediaのデータをWord2Vecで遊んでみます。
わざわざ書き起こすのは、メモしておかないと、遊び方を忘れてしまうためです・・・。

今回の環境は下記のような感じです。
  • CentOS 7.4
  • MeCab 0.996
MeCabとneologdは以前の記事を参照していただくか、groongaなどのリポジトリからインストールしておいてください。

準備

今回もgit wgetを用意しておくのと、Pythonの3.xの環境を用意しておきます。word2vecをコンパイルするのにgccも必要です。MeCaabをソースから入れた場合は、すでに入っているものもあると思いますので、その場合は読み飛ばしてください。
yum install git wget epel-release
yum install python34 python34-pip
yum install gcc

Wikipediaのわかち書きテキストを作成する

Wikipediaのデータの切り出しには「Wikipedia_Extractor」というツールを使います。 gitで落としてきます。
mkdir src
cd ~/src/
git clone https://github.com/attardi/wikiextractor
cd wikiextractor/
Wikipediaのデータは公開されているので、ダウンロードします。100万件以上、2.4GB分の記事があります。
wget https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2
落としてきたファイルは、圧縮されたXMLなのですが、テキストに近い形式とするため先ほどの「Wikipedia_Extractor」を使用します。なお、落としてきたファイルは解凍する必要はありません。下記のように指定すると、解凍してタグを除去して5Mごとに分割してくれます。
mkdir corpus
python3 WikiExtractor.py -b 5M -o corpus jawiki-latest-pages-articles.xml.bz2
実行すると下記のように処理が完了したWikipediaのページの名称とページのIDが表示されます。
INFO: 313154    クラーク郡 (イリノイ州)
INFO: 313155    ジョン・マルコヴィッチ
INFO: 313156    カート・ラッセル
INFO: 313157    モスリン
INFO: 313158    マイク・マイヤーズ
INFO: 313160    大阪府道197号深井畑山宿院線



Wikipediaの日本語データを正規化を行うため、mecab-ipadic-neologdのページから正規化用のPythonコードをコピーしてきて「normalize_neologd.py」という名前で保存します。
一応下にも書いておきますが・・・neologdのサイトを確認したほうが良いと思います。
ファイル: normalize_neologd.py
# encoding: utf8
from __future__ import unicode_literals
import re
import unicodedata

def unicode_normalize(cls, s):
    pt = re.compile('([{}]+)'.format(cls))

    def norm(c):
        return unicodedata.normalize('NFKC', c) if pt.match(c) else c

    s = ''.join(norm(x) for x in re.split(pt, s))
    s = re.sub('-', '-', s)
    return s

def remove_extra_spaces(s):
    s = re.sub('[  ]+', ' ', s)
    blocks = ''.join(('\u4E00-\u9FFF',  # CJK UNIFIED IDEOGRAPHS
                      '\u3040-\u309F',  # HIRAGANA
                      '\u30A0-\u30FF',  # KATAKANA
                      '\u3000-\u303F',  # CJK SYMBOLS AND PUNCTUATION
                      '\uFF00-\uFFEF'   # HALFWIDTH AND FULLWIDTH FORMS
                      ))
    basic_latin = '\u0000-\u007F'

    def remove_space_between(cls1, cls2, s):
        p = re.compile('([{}]) ([{}])'.format(cls1, cls2))
        while p.search(s):
            s = p.sub(r'\1\2', s)
        return s

    s = remove_space_between(blocks, blocks, s)
    s = remove_space_between(blocks, basic_latin, s)
    s = remove_space_between(basic_latin, blocks, s)
    return s

def normalize_neologd(s):
    s = s.strip()
    s = unicode_normalize('0-9A-Za-z。-゚', s)

    def maketrans(f, t):
        return {ord(x): ord(y) for x, y in zip(f, t)}

    s = re.sub('[˗֊‐‑‒–⁃⁻₋−]+', '-', s)  # normalize hyphens
    s = re.sub('[﹣-ー—―─━ー]+', 'ー', s)  # normalize choonpus
    s = re.sub('[~∼∾〜〰~]', '', s)  # remove tildes
    s = s.translate(
        maketrans('!"#$%&\'()*+,-./:;<=>?@[¥]^_`{|}~。、・「」',
              '!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」'))

    s = remove_extra_spaces(s)
    s = unicode_normalize('!”#$%&’()*+,-./:;<>?@[¥]^_`{|}〜', s)  # keep =,・,「,」
    s = re.sub('[’]', '\'', s)
    s = re.sub('[”]', '"', s)
    return s

if __name__ == "__main__":
    assert "0123456789" == normalize_neologd("0123456789")
    assert "ABCDEFGHIJKLMNOPQRSTUVWXYZ" == normalize_neologd("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    assert "abcdefghijklmnopqrstuvwxyz" == normalize_neologd("abcdefghijklmnopqrstuvwxyz")
    assert "!\"#$%&'()*+,-./:;<>?@[¥]^_`{|}" == normalize_neologd("!”#$%&’()*+,-./:;<>?@[¥]^_`{|}")
    assert "=。、・「」" == normalize_neologd("=。、・「」")
    assert "ハンカク" == normalize_neologd("ハンカク")
    assert "o-o" == normalize_neologd("o₋o")
    assert "majikaー" == normalize_neologd("majika━")
    assert "わい" == normalize_neologd("わ〰い")
    assert "スーパー" == normalize_neologd("スーパーーーー")
    assert "!#" == normalize_neologd("!#")
    assert "ゼンカクスペース" == normalize_neologd("ゼンカク スペース")
    assert "おお" == normalize_neologd("お             お")
    assert "おお" == normalize_neologd("      おお")
    assert "おお" == normalize_neologd("おお      ")
    assert "検索エンジン自作入門を買いました!!!" == \
        normalize_neologd("検索 エンジン 自作 入門 を 買い ました!!!")
    assert "アルゴリズムC" == normalize_neologd("アルゴリズム C")
    assert "PRML副読本" == normalize_neologd("   PRML  副 読 本   ")
    assert "Coding the Matrix" == normalize_neologd("Coding the Matrix")
    assert "南アルプスの天然水Sparking Lemonレモン一絞り" == \
        normalize_neologd("南アルプスの 天然水 Sparking Lemon レモン一絞り")
    assert "南アルプスの天然水-Sparking*Lemon+レモン一絞り" == \
        normalize_neologd("南アルプスの 天然水- Sparking* Lemon+ レモン一絞り")

さらに、パイプで繋げられるように標準入力で入ってきたデータを正規化できるように微妙に小細工を入れつつラッパーを作ります。
ファイル: conv.py
import sys
import normalize_neologd as nneo
import re
for line in sys.stdin:
    line = line.replace("。","。\n")
    line = re.sub(r'<[^\]]*>', '', line)
    print(nneo.normalize_neologd(line))

先ほどまでに分解したWikipediaのデータをマージして正規化して分かち書きにします。ここまでの手順で、wikipediaの正規化されたプレーンテキストが手に入りました。
cat corpus/*/* | python3 conv.py | mecab -b 32768 -Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd > wiki.mecab.txt
mecabのデフォルトのバッファ8192Byteだと「input-buffer overflow. The line is split. use -b #SIZE option.」というエラーが出ますので、-bで32768を指定するとエラーが出なくなりました。実際にはもう少し小さくてもいいのかもしれません。

word2vecをコンパイル

取ってきてmakeします。できた実行ファイルは手動でPATHの通ったところにコピーします。
cd ~/src
git clone https://github.com/dav/word2vec
cd word2vec/src
make
cp -p ../bin/* /usr/local/bin/.



学習とパラメータの概要

ここまで来たら、実行するだけです。下記のコマンドで学習を開始します。
cd ~/src/wikiextractor/
time word2vec -train wiki.mecab.txt -output wiki.mecab.word2vec.size200.bin -size 200 -window 5 -sample 1e-5 -negative 5 -binary 1
学習結果は-outputで指定し、wiki.mecab.binに保存されます。

オプションについて詳しく知りたい場合は、本家word2vecの説明Mikolov氏の論文など有用な情報がいっぱいころがってますのでそちらを読んで確認してください。

ここでは、ざっくりとした概要だけ。
オプション 内容
-train 学習する元となるファイルを指定します。今回は作成したファイルを指定します。
-size ベクトルの次元数を指定します。サンプル同様200を指定しましたが、wikipediaのデータを基とする場合400とか600の方が良いかもしれません。4GBのメモリだと300程度が限界だと思います。400を指定すると途中で強制終了しましたので、急遽仮想マシンのメモリを8GBで割り当てなおしました。
-window 関連性があると判断する前後の単語数。デフォルトのskip-gramなら10くらい。CBOWなら5を指定すればいいようだ。
-sample 出現回数の多い単語を学習の結果から除外するための閾値です。(1e-3〜1e-5の範囲で指定)これは、MeCabで分かち書きをした結果を見ればわかりますが、「の」や「が」や「ある」などどうでもよい語が頻出するためで、このようなものを学習内容から除外することができるみたいです。このQiitaの記事が大変分かりやすかったです。
-negative 指定した数の単語の関連性を低く設定します。低く設定されるのはWindowで指定した値から外れた単語のうちランダムに選ばれた単語になります。(5-10くらい。使わない場合は0を指定。)
-binary 1に指定すると結果をバイナリ形式で保存します。


Distanceで似た単語を探す

さっそく実行してみます。
distance wiki.mecab.word2vec.size200.bin
Enter word or sentence (EXIT to break): Python

Word: Python Position in vocabulary: 24421

Word Cosine distance
-------------------------------------
Perl 0.925127
Java 0.910050
Ruby 0.898116
C++ 0.892958
JavaScript 0.890242
プログラミング言語 0.885551
ライブラリ 0.884654
スクリプト言語 0.879306
PHP 0.878212
Lua 0.876160
C# 0.875964
コンパイラ 0.870334
VB.NET 0.861130
ソースコード 0.860082
C言語 0.858654
C/C++ 0.857195
統合開発環境 0.850669
処理系 0.849689

ふむ。
Enter word or sentence (EXIT to break): C言語

Word: C言語 Position in vocabulary: 16918

Word Cosine distance
-------------------------------------
コンパイラ 0.919928
プログラミング言語 0.915335
C++ 0.909260
処理系 0.893756
C/C++ 0.888579
インタプリタ 0.886861
バイトコード 0.885650
FORTRAN 0.880558
Smalltalk 0.878671
プリプロセッサ 0.877056
Java 0.874642
Haskell 0.873704
Perl 0.873273
Lua 0.873125
D言語 0.871268
高級言語 0.867297
Objective-C 0.867137
結構いい結果が得られているんじゃないでしょうか・・・。

word-analogyで似たベクトルの単語を探す


「A の時は B、C の時は何?」といった関連性が類似する(ベクトルの方向性が同じ)ものを答えてくれます。
日本の時は東京、アメリカの時は?というような内容に答えてくれます。
先ほどの学習結果が使えますので、新たな学習は必要ありません。


word-analogy wiki.mecab.word2vec.size200.bin
Enter three words (EXIT to break): 日本 東京 アメリカ

Word: 日本 Position in vocabulary: 38001
Word: 東京 Position in vocabulary: 348
Word: アメリカ Position in vocabulary: 172

Word Distance
------------------------------------------------------------------------
ニューヨーク 0.658165
アナザー・カントリー 0.626435
フォー・ソーシャルリサーチ 0.611632
米国 0.611108
シンシナティー 0.610880
きちんと正解が1番最初に出てきました。

Enter three words (EXIT to break): ボストン アメリカ バルセロナ

Word: ボストン Position in vocabulary: 4817
Word: アメリカ Position in vocabulary: 172
Word: バルセロナ Position in vocabulary: 7925

Word Distance
------------------------------------------------------------------------
スペイン 0.733546
マドリード 0.644633
イタリア 0.634427
ブラジル 0.631908
フランス 0.630570
これも正しい回答が一番最初に表示されました。

Enter three words (EXIT to break): 日本 東京 中国

Word: 日本 Position in vocabulary: 55
Word: 東京 Position in vocabulary: 348
Word: 中国 Position in vocabulary: 329

Word Distance
------------------------------------------------------------------------
北京 0.674370
上海 0.644645
杭州 0.610560
清華大学 0.595791
天津 0.593921
こういうロケーションにかかる部分は得意みたいです。
ほとんど、間違えなく答えが出てきます。

次に失敗してしまったパターンを・・・
Enter three words (EXIT to break): コンピュータ CPU 人間

Word: コンピュータ Position in vocabulary: 1816
Word: CPU Position in vocabulary: 4492
Word: 人間 Position in vocabulary: 500

Word Distance
------------------------------------------------------------------------
ラグズ 0.632038
ベオク 0.616553
破壊衝動 0.605122
ヤセイ 0.604995
スラード 0.604204
ソウルジェム 0.604126
ノドス 0.597569
凶暴性 0.595487
神殺し 0.592517
悪の力 0.590891
イリシッド 0.590625
卑小 0.590289
生命力 0.589909
紅世の王 0.589552
ケンク 0.587762
天空聖者 0.587749
虚無 0.583919
ゾラーク・ゾラーン 0.581554
大魔王バーン 0.581542
シャダーカイ 0.580933
レオモン 0.580893
ビシャモン 0.580755
人間らしい 0.580128
血肉 0.579877
獣性 0.579627
ええと、ほとんどなに言ってるのかわからないんですけど・・・。
これは、脳と答えてほしかった;;
なんとか、「血肉」が答えらしいでしょうかね・・・。

近いベクトルの単語を探してくるだけですので、もう少し次元を増やしたりすることで何とかなるのかもしれません。

Word2Phrase

これは、フレーズを検出してくれるプログラムです。フレーズとして扱うべきものを「_」で繋げることによって、word2vecでは単語として認識されます。例えば、分かち書きで「日本」「語」と2つの語に分割されてしまったものを「_」で繋げて「日本_語」とすることで、1つの単位として扱います。

複数回実行することでより長い単語を連結することができますが、本来はコードに手を入れた方が良いであろうことと、回数を重ねすぎると連結してほしくないところまで連結し始めますので、注意が必要です。
ではやってみます。

1回目

time word2phrase -train wiki.mecab.txt -output wiki.mecab.phrases.txt -threshold 500 -debug 2
Starting training using file wiki.mecab.txt
Words processed: 449700K Vocab size: 39165K
Vocab size (unigrams + bigrams): 22711828
Words in train file: 449716604
Words written: 449700K
real 113m6.868s
user 39m28.540s
sys 25m15.209s
出来上がったテキストファイルを開いてみると、下記のような単語が見つかりました。分かち書きでは「イスラム」「圏」と2単語に分かれてしまいましたが、word2phraseで「_」で連結されていることが確認できます。
アルバニア_語
古代_ギリシャ語
ポルトガル語_圏
イスラム_圏
体性感覚_野
日本語_学習者
出来上がったテキストを、もう一度word2phraseにかけてみます。

2回目

time word2phrase -train wiki.mecab.phrases.txt -output wiki.mecab.phrases2.txt -threshold 500 -debug 2
Starting training using file wiki.mecab.phrases.txt
Words processed: 437300K Vocab size: 43443K
Vocab size (unigrams + bigrams): 25057778
Words in train file: 437363941
Words written: 437300K
real 130m48.335s
user 27m41.600s
sys 47m3.203s
先ほどからさらに連結された単語の長さが増えていることが確認できます。「GHOST_IN_THE_SHELL/攻殻機動隊」を統計で認識できるのはすごい!と思いました。
十_六_進
千_数_百
カダイ_語_族
上位_10位_以内
被_修飾_語
WBC_世界_ヘビー級_王者
数_百_万_円
GHOST_IN_THE_SHELL/攻殻機動隊
単行_本_未_収録
大阪_芸術大学_芸術学部_映像
法起寺_式_伽藍_配置
武王_伐_紂_平話
先ほど作成されたテキストを、さらにもう一度word2phraseにかけてみます。

3回目

time word2phrase -train wiki.mecab.phrases2.txt -output wiki.mecab.phrases3.txt -threshold 500 -debug 2
Starting training using file wiki.mecab.phrases2.txt
Words processed: 433600K Vocab size: 44808K
Vocab size (unigrams + bigrams): 25779937
Words in train file: 433666534
Words written: 433600K
real 166m28.564s
user 27m51.933s
sys 64m7.211s
ちょっと、やりすぎ感が出てきました。そこまで連結しなくとも機械学習で認識できるであろう物と関連性のない単語が連結されている部分が増えてきたように思います。2回目の「十_六_進」までは良いと思うけど、「十_六_進_表記」まで連結する必要はないでしょう・・・。
十_六_進_表記
家庭_用_電気_機械_器具
オーストリア_福音主義_教会_アウクスブルク信仰告白_派
全世界_ウェイト_制_空手道_選手権_大会
大阪_芸術大学_芸術学部_美術_学科
全国_公営_競馬_主催者_協議会_会長
ここで出来上がったファイルをもとにword2vecで学習させると、また異なった結果が得られると思います。
「_」は必要ないので削ってしまってもよいのかもしれません。

2017年9月10日日曜日

Python3からChaSenを使う@CentOS 7


ChaSenは奈良先端科学技術大学院大学松本研究室で開発された日本語の形態素解析システムです。2000年ころに松本教授自ら書かれた記事が公開されていました。

今回のざっくりとした環境は下記のとおりです。CentOS7は最小インストールした直後の状態を想定しています。
  • ChaSen 2.4.5
  • Python 3.4
  • CentOS 7.3
今回もコンパイルして入れます・・・が、Python 3から呼び出せるバインディングが見つかりませんでしたので、プロセスを呼んで結果を取得します。

準備

nkfとgcc、gcc-c++をインストールします。
yum install nkf gcc gcc-c++

Dartsのインストール

ChaSenを入れる前にDartsというライブラリが必要になりますので、インストールします。
cd ~/download
wget http://chasen.org/~taku/software/darts/src/darts-0.32.tar.gz
cd ~/src
tar xzvf ../download/darts-0.32.tar.gz
cd darts-0.32/
./configure
make
make install

ChaSen本体のインストール

本体をインストールします。
cd ~/download
wget 'https://ja.osdn.net/frs/redir.php?m=jaist&f=%2Fchasen-legacy%2F56305%2Fchasen-2.4.5.tar.gz' -O chasen-2.4.5.tar.gz
cd ~/src
tar xzvf ../download/chasen-2.4.5.tar.gz
cd chasen-2.4.5/
./configure
make
make install

chasen辞書のインストール

辞書は別途用意されているので、ダウンロードしてインストールします。
wget 'https://ja.osdn.net/frs/redir.php?m=ymu&f=%2Fipadic%2F24435%2Fipadic-2.7.0.tar.gz' -O ipadic-2.7.0.tar.gz
cd ~/src/
tar xzvf ../download/ipadic-2.7.0.tar.gz
cd ipadic-2.7.0/
./configure
make
make install
chasen自体はEUCをベースにしていますので、そのままだと文字化けします。
echo "これは、テストです。"|nkf -e|chasen|nkf -w
これ    コレ    これ    名詞-代名詞-一般
は      ハ      は      助詞-係助詞
、      、      、      記号-読点
テスト  テスト  テスト  名詞-サ変接続
です    デス    です    助動詞  特殊・デス      基本形
。      。      。      記号-句点
EOS
仕方ないので、nkfを両端に挟んでみましたが、たぶんコストも大きくなりますし、見た目が悪いw

辞書をutf-8に変更

なんとか、utf-8に変更したいと思って方法を探していたところ、Qiitaにいい記事がありましたので、参考にしつつut-8に変更を行いました。
cd ~/src/ipadic-2.7.0/
make clean
find -name '*.dic' | xargs nkf --overwrite -w
find -name '*.cha' | xargs nkf --overwrite -w
`chasen-config --mkchadic`/makemat -i w
`chasen-config --mkchadic`/makeda -i w chadic *.dic
./configure
make
make install
nkf -w --overwrite /usr/local/etc/chasenrc
この状態でテストを行います。chasenには-iwのオプションを付けて呼び出します。
echo 'これは、テストです。'|chasen -iw
これ    コレ    これ    名詞-代名詞-一般
は      ハ      は      助詞-係助詞
、      、      、      記号-読点
テスト  テスト  テスト  名詞-サ変接続
です    デス    です    助動詞  特殊・デス      基本形
。      。      。      記号-句点
EOS
ちなみに、ChaSenの辞書も古いためそのままでは、新しめの単語には対応できません。
echo 'クラウド'|chasen -iw
クラ    クラ    クラ    名詞-固有名詞-一般
ウド    ウド    ウド    名詞-一般
EOS

Python3から呼び出す

これは、wrapperが見つかりませんでしたので、プロセスを呼び出す要領で標準出力を配列に格納します。 ほかに何か良い方法があれば、だれか教えてください。
ファイル: chasen.py
import subprocess

def strip_cmd_injection(instr):
    inj = [";", "|", "&", "`", "(", ")", "$", "<", ">", "*", "?", "{", "}", "[", "]", "!", "\n"]
    for s in inj:
        instr = instr.replace(s,"")
    return instr

def chasen(arg):
    arg = strip_cmd_injection(arg)
    cmd = "echo {0} | chasen -iw".format(arg)
    proc = subprocess.Popen(
        cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = proc.communicate()
    if stderr != b'':
        raise(Exception(stderr.decode("utf-8")))

    for line in stdout.decode('utf-8').split("\n"):
        if (line == "EOS"):
            break
        yield line.split("\t")

if __name__ == '__main__':
    import sys
    for line in sys.stdin:
        for cha in chasen(line):
            print (cha)
実行すると、下記のような結果が得られます。各要素が配列になっているので、取り出して使うことを想定しています。
echo "これはてすとです。" | python3 sample.py
['これ', 'コレ', 'これ', '名詞-代名詞-一般', '', '']
['はて', 'ハテ', 'はて', '副詞-一般', '', '']
['す', 'ス', 'す', '名詞-一般', '', '']
['と', 'ト', 'と', '助詞-格助詞-一般', '', '']
['で', 'デ', 'で', '助詞-格助詞-一般', '', '']
['す', 'ス', 'する', '動詞-自立', 'サ変・スル', '文語基本形']
['。', '。', '。', '記号-句点', '', '']

2017年9月9日土曜日

Python3からJUMAN++を使う@CentOS7



JUMAN++は、京都大学黒橋・河原研究室で開発されている日本語の形態素解析システムです。RNNを使った言語モデルを使用しているとのことで、MecabやJUMANより自然な感じの形態素解析ができるらしいです。(雑な説明でごめんなさい)
今回のざっくりとした環境は下記のとおりです。CentOS7は最小インストールした直後の状態を想定しています。
  • JUMAN 1.02
  • Python 3.4
  • CentOS 7.3
今回もコンパイルして入れます・・・が、Boost C++ Libraryというものが必要です。 CentOS 7のリポジトリにはJUMAN++に要求されるバージョン(1.58)より古いバージョン(1.53)しかないので、仕方がないのでこちらも最新のもの(1.65)をコンパイルしてインストールします。
とても時間がかかりました。

準備

とりあえず、gccとc++が必要ですのでインストールします。それとPython-develが必要になりますので、インストールします。 (Python3-develでは、boostのコンパイル時にエラーが出るようです。)
yum install gcc gcc-c++ python-devel
yum install epel-release
yum install python34 python34-devel python34-pip

Boost C++ Libraries のインストール

Boostの公式サイトからソースをダウンロードします。
インストール方法は公式サイトの5.2.1に書いてあるみたいです。
なお、Boost C++ Librariesのインストールに当たっては、「boostjp : Boost日本語情報サイト」が大変参考になりました。
mkdir download
mkdir src
cd download
wget https://dl.bintray.com/boostorg/release/1.65.1/source/boost_1_65_1.tar.gz
cd ~/src
tar xzvf ../download/boost_1_65_1.tar.gz

cd boost_1_65_1/
./bootstrap.sh

./b2 install -j2

-j2の部分はコア数に応じて増やした方が、コンパイルが早く終わると思います。

JUMAN++のインストール

長いコンパイルが終わるとやっとJUMAN++のインストールに入れます。 JUMAN++自体は、コンパイルしてインストールするだけです。
cd ~/download/
wget 'http://nlp.ist.i.kyoto-u.ac.jp/DLcounter/lime.cgi?down=http://lotus.kuee.kyoto-u.ac.jp/nl-resource/jumanpp/jumanpp-1.02.tar.xz&name=jumanpp-1.02.tar.xz' -O jumanpp-1.02.tar.xz
cd ~/src
tar Jxfv ../download/jumanpp-1.02.tar.xz
cd jumanpp-1.02/

./configure
make
make install
正常に動作するか確認します。
echo "これは、テストです。" | jumanpp

これ これ これ 指示詞 7 名詞形態指示詞 1 * 0 * 0 NIL
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
、 、 、 特殊 1 読点 2 * 0 * 0 NIL
テスト てすと テスト 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:テスト/てすと カテゴリ:抽象物 ドメイン:教育・学習"
です です だ 判定詞 4 * 0 判定詞 25 デス列基本形 27 NIL
。 。 。 特殊 1 句点 1 * 0 * 0 NIL
EOS
上記のような表示されれば、インストールは成功です。

Pythonバインディングのインストール

PyKNPはJUMANをインストールした際に合わせてインストールしている場合は、読み飛ばしてください。
pip3 install six
cd ~/download/
wget 'http://nlp.ist.i.kyoto-u.ac.jp/DLcounter/lime.cgi?down=http://lotus.kuee.kyoto-u.ac.jp/nl-resource/pyknp/pyknp-0.3.tar.gz&name=pyknp-0.3.tar.gz' -O pyknp-0.3.tar.gz
cd ~/src/
tar xzvf ../download/pyknp-0.3.tar.gz
cd pyknp-0.3/
python3 setup.py install



テストプログラムを書いて実行してみます。
ファイル: test.py
from pyknp import Jumanpp

j = Jumanpp()
r = j.analysis('これは、テストです。')
for m in r.mrph_list():
    print (m.midasi, m.yomi, m.genkei, m.hinsi, m.bunrui, m.katuyou1, m.katuyou2, m.imis, m.repname)
下記のように表示されれば、成功です。
python3 test.py 
これ これ これ 指示詞 名詞形態指示詞 * * NIL 
は は は 助詞 副助詞 * * NIL 
、 、 、 特殊 読点 * * NIL 
テスト てすと テスト 名詞 サ変名詞 * * 代表表記:テスト/てすと カテゴリ:抽象物 ドメイン:教育・学習 テスト/てすと
です です だ 判定詞 * 判定詞 デス列基本形 NIL 
。 。 。 特殊 句点 * * NIL

Python3からJUMANを使う@CentOS7


JUMANは、京都大学黒橋・河原研究室で開発されている日本語の形態素解析システムです。これを入れると、日本語の形態素解析ができます。(雑な説明でごめんなさい)
今回のざっくりとした環境は下記のとおりです。CentOS7は最小インストールした直後の状態を想定しています。
  • JUMAN 7.01
  • Python 3.4
  • CentOS 7.3

準備

コンパイルしてインストールしますので、gccを入れておいてください。
yum install bzip2 gcc
yum install epel-release
yum install python34 python34-devel python34-pip

JUMANのインストール

ダウンロードしてコンパイルしてインストールするだけです。あまり迷うところもないような気がします。
mkdir download
mkdir src cd download/ wget 'http://nlp.ist.i.kyoto-u.ac.jp/DLcounter/lime.cgi?down=http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/juman/juman-7.01.tar.bz2&name=juman-7.01.tar.bz2' -O juman-7.01.tar.bz2 cd ~/src tar xvf ../download/juman-7.01.tar.bz2 cd juman-7.01/ ./configure make make install
実行すると下記のような出力を得ることができます。
echo "これは、テストです。" | juman
これ これ これ 指示詞 7 名詞形態指示詞 1 * 0 * 0 NIL
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
、 、 、 特殊 1 読点 2 * 0 * 0 NIL
テスト てすと テスト 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:テスト/てすと カテゴリ:抽象物 ドメイン:教育・学習"
です です だ 判定詞 4 * 0 判定詞 25 デス列基本形 27 NIL
。 。 。 特殊 1 句点 1 * 0 * 0 NIL
EOS

Pythonバインディングのインストール

あとは、Pythonから呼び出せるようにWrapperをインストールするだけです。PyKNPというPythonバインディングをインストールすることになりますが、pipでは入りませんので、京都大学からダウンロードして、インストールします。また、Pythonのsixというライブラリも必要になりますので、あらかじめインストールしておきます。(sixはPython2とPython3の互換ライブラリです)
pip3 install six
cd ~/download/
wget 'http://nlp.ist.i.kyoto-u.ac.jp/DLcounter/lime.cgi?down=http://lotus.kuee.kyoto-u.ac.jp/nl-resource/pyknp/pyknp-0.3.tar.gz&name=pyknp-0.3.tar.gz' -O pyknp-0.3.tar.gz
cd ~/src/
tar xzvf ../download/pyknp-0.3.tar.gz
cd pyknp-0.3/
python3 setup.py install
テストプログラムを作ります。
ファイル: test.py
from pyknp import Juman

j = Juman()
r = j.analysis('これは、テストです。')
for m in r.mrph_list():
    print (m.midasi, m.yomi, m.genkei, m.hinsi, m.bunrui, m.katuyou1, m.katuyou2, m.imis, m.repname)
無事動作することが確認できました。
python3 test.py 
これ これ これ 指示詞 名詞形態指示詞 * * NIL
は は は 助詞 副助詞 * * NIL
、 、 、 特殊 読点 * * NIL
テスト てすと テスト 名詞 サ変名詞 * * 代表表記:テスト/てすと カテゴリ:抽象物 ドメイン:教育・学習 テスト/てすと
です です だ 判定詞 * 判定詞 デス列基本形 NIL
。 。 。 特殊 句点 * * NIL
JUMANは、Wikipediaから辞書を持ってきているようですので、MeCabと違って辞書などを登録しなくとも新しめの単語を認識することができます。
echo "クラウド"|juman

クラウド クラウド クラウド 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:Wikipedia Wikipedia多義"
EOS

Python3からMeCabを使う@CentOS7


MeCabをソースからインストールします。
groongaのリポジトリ追加に抵抗がない場合は、そちらでも良いと思います。

今回のざっくりとした環境は下記のとおりです。CentOS7は最小インストールした直後の状態を想定しています。
  • CentOS 7.3
  • Python 3.4
  • mecab 0.996
  • ipadic 2.7.0-20070801

準備

Python3.4を入れるためにEPELのリポジトリを入れます。 あと、gcc-c++が入っていないと、./configureした際に「configure: error: Your compiler is not powerful enough to compile MeCab.」とエラーが出ますので、gcc-c++もインストールしておきます。
yum install epel-release
yum install python34 python34-devel python34-pip
yum install gcc gcc-c++
yum install wget

MeCab本体のインストール

まずは、本体のインストールを行います。ついでに辞書もダウンロードしてきます。configureのオプションでutf-8を入れるのを忘れないようにします。
mkdir download
mkdir src
cd download
wget 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE' -O mecab-0.996.tar.gz
wget 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM' -O mecab-ipadic-2.7.0-20070801.tar.gz
cd ~/src
tar xzvf ../download/mecab-0.996.tar.gz
cd mecab-0.996/
./configure --with-charset=utf8
make
make install
辞書を入れる前に実行してみるとエラーが出ます・・・。
mecab
param.cpp(69) [ifs] no such file or directory: /usr/local/lib/mecab/dic/ipadic/dicrc

MeCab辞書のインストール

辞書のconfigureオプションにもutf-8を指定します。
cd ..
tar xvzf ../download/mecab-ipadic-2.7.0-20070801.tar.gz
cd mecab-ipadic-2.7.0-20070801/
./configure --with-charset=utf8
make
sudo make install
とりあえず実行してみて、下記のように分解されればインストールは終了です。
echo "これは、テストです。"|mecab
これ    名詞,代名詞,一般,*,*,*,これ,コレ,コレ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
、      記号,読点,*,*,*,*,、,、,、
テスト  名詞,サ変接続,*,*,*,*,テスト,テスト,テスト
です    助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。      記号,句点,*,*,*,*,。,。,。
EOS

Pythonバインディングのインストール

次にPython3から呼び出せるように環境を整えていきます。pipでmecab-python3というライブラリを入れます。
pip3 install mecab-python3
ld.so.confを編集して2行目を加えます。
ファイル: /etc/ld.so.conf
include ld.so.conf.d/*.conf
/usr/local/lib/ # この行を追加
ldconfigで共有ライブラリの依存関係を更新します。
ldconfig
/usr/local/lib/をld.so.confに書く前に実行すると下記のエラーが表示されます。
ImportError: libmecab.so.2: cannot open shared object file: No such file or directory
適当なプログラムを書いてみます。
ファイル: test.py
import MeCab

mecab = MeCab.Tagger("-Ochasen")
print(mecab.parse("これは、テストです。"))
実行して、先ほどと同様の結果が得られれば、成功です。
python3 test.py
これ    コレ    これ    名詞-代名詞-一般
は      ハ      は      助詞-係助詞
、      、      、      記号-読点
テスト  テスト  テスト  名詞-サ変接続
です    デス    です    助動詞  特殊・デス      基本形
。      。      。      記号-句点
EOS
Pythonから呼び出した結果は、Tabでsplitして変数に格納して使うか、-Ochasenを-Owakachiにして空白などで結合して使うことが多いようです。 また、プログラムが古めですので、実際に使うには辞書が不足しているため、辞書を追加したほうがいいです。
echo "クラウド"|mecab
クラ    名詞,固有名詞,一般,*,*,*,クラ,クラ,クラ
ウド    名詞,一般,*,*,*,*,ウド,ウド,ウド
EOS
wikipediaの見出しを使うことが多いみたいです。

mecab-ipadic-NEologdの追加

辞書が古いので、新しい辞書を追加します。gitとpatchが必要になりますので入れておきます。
yum install git patch
mecab-ipadic-neologdをダウンロードしてインストールします。途中で、インストールするか聞かれますので、「yes」と入力してください。辞書は「/usr/local/lib/mecab/dic/mecab-ipadic-neologd」にインストールされます。
cd ~/src
git clone https://github.com/neologd/mecab-ipadic-neologd.git
cd mecab-ipadic-neologd/
./bin/install-mecab-ipadic-neologd -n
#長々とメッセージが流れた後にインストールするか聞かれます。
[install-mecab-ipadic-NEologd] : Do you want to install mecab-ipadic-NEologd? Type yes or no.
yes 
インストールが完了したら、先ほどは認識できなかった「クラウド」という単語が登録されているか確認してみます。
echo "クラウド"|mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd
クラウド        名詞,固有名詞,人名,一般,*,*,CLOUD,クラウド,クラウド
EOS
Pythonから呼び出すときもこのディレクトリを含めて呼び出すことを忘れないようにしてください。
mecab = MeCab.Tagger("-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd")
なお、NEologdのGitHUBのページには正規化の方法が詳しく書いてあり、正規化用のPythonのコードもついていて大変参考になります。なお、この正規化をやったうえで、個人的にさらに気になる点があるとすれば、下記の部分です。
  • 数字と数字の間のカンマの削除(12,345,678とか)
  • カタカナの単位(メートルとか)
  • 100mm以上のミリメートル
  • 漢数字(11兆9000億円→11900000000000円)