Javaの有名な形態素解析器でありながら、長らく「公式サイトどこ?」な状況だったSenとGoSenですが、最近はlucene-gosenなるGoSenベースのライブラリがちゃんと管理された状態で公開されてるとか。
lucene-gosen
http://code.google.com/p/lucene-gosen/
しかもこの子は辞書内包なのでjarを落とすだけで使えて、Lucene用AnalyzerやSolr用Toknizerも付いていて、日本語の検索用インデックスを貼る時に便利な各種フィルタも用意されているという、至れり尽くせりな構成になっているとか。
これは触ってみねばということで、とりあえず簡単な形態素解析、辞書の追加、Luceneでの利用、Solrでの利用を試してみました。
ちなみに上のURLでCommiterのところに名前が出ているKoji SekiguchiさんはSolr本でも有名な関口宏司さん、Robert MuirさんはLucene/Solrのコミッタの方だそうです。
まずは下記サイトからjarを落とす。
Downloads - lucene-gosen
http://code.google.com/p/lucene-gosen/downloads/list
ipadicとnaist-chasenの2種類がいるので、好みの方を選択する。とくにこだわりがなければ、使っている人が多いIPAの方を選択すればよろしいかと。
ダウンロードしたjarをクラスパスに入れて、下記のようなコードを実行すれば形態素解析できる。
// この3行で解析できる
StringTagger tagger = SenFactory.getStringTagger(null);
List<Token> tokens = new ArrayList<Token>();
tagger.analyze("もう眠い", tokens);
// 解析結果の中身をいろいろ出力してみる
for (Token token : tokens) {
System.out.println("=====");
System.out.println("surface : " + token.getSurface());
System.out.println("cost : " + token.getCost());
System.out.println("length : " + token.getLength());
System.out.println("start : " + token.getStart());
System.out.println("basicForm : " + token.getMorpheme().getBasicForm());
System.out.println("conjugationalForm : " + token.getMorpheme().getConjugationalForm());
System.out.println("conjugationalType : " + token.getMorpheme().getConjugationalType());
System.out.println("partOfSpeech : " + token.getMorpheme().getPartOfSpeech());
System.out.println("pronunciations : " + token.getMorpheme().getPronunciations());
System.out.println("readings : " + token.getMorpheme().getReadings());
}
上記のコードは「もう眠い」という言葉を解析している。解析結果は以下。
==============================
surface : もう
cost : 2699
length : 2
start : 0
basicForm : *
conjugationalForm : *
conjugationalType : *
partOfSpeech : 副詞-一般
pronunciations : [モー]
readings : [モウ]
==============================
surface : 眠い
cost : 5782
length : 2
start : 2
basicForm : *
conjugationalForm : 基本形
conjugationalType : 形容詞・アウオ段
partOfSpeech : 形容詞-自立
pronunciations : [ネムイ]
readings : [ネムイ]
上記のように品詞や読み、コスト値なども取得できる。
ちなみにこの辺の機能はNOT Thread Safeらしい。同じJava製形態素解析器のIgoやGomokuのTaggerクラスはスレッドセーフだった気がする。
未知語の場合はpartOfSpeechに未知語という文字列が設定される。
JavaDocはこちら
http://lucene-gosen.googlecode.com/svn/branches/javadoc/api/index.html
ファイルを読み込みながら一文ずつ解析したい場合は、StreamTaggerを使用する。長い文章を徐々に解析していく時に便利。
試しに青空文庫から太宰治の斜陽を持ってきて解析してみる。
StringTagger stringTagger = SenFactory.getStringTagger(null);
Reader reader = new InputStreamReader(new FileInputStream("shayo.txt"), "shift_jis");
StreamTagger tagger = new StreamTagger(stringTagger, reader);
while (tagger.hasNext()) {
Token token = tagger.next();
System.out.println(token.getSurface());
}
斜陽
太宰
治
-------------------------------------------------------
【
テキスト
中
に
現れる
記号
について
】
<以下略>
ちゃんと全文解析できた。StreamTaggerは文字列をバッファに入れて少しずつ解析しているらしい。
辞書の編集を行ないたい場合は、ソースをチェックアウトして用意されているbuild.xmlを利用すると楽。
$ svn checkout http://lucene-gosen.googlecode.com/svn/trunk/ lucene-gosen
とりあえず普通にbuildする。
$ cd lucene-gosen
$ ant
これでdistの下にIPA辞書が同梱されたjarが、dictionaryの下にipadic辞書が作成される。
NAISTの辞書を選びたい場合は以下のように実行する。
$ ant -Ddictype=naist-chasen
生成される辞書に単語の追加を行う場合は、antする時に以下のように引数を入れれば、任意のcsvファイルの内容を辞書に追加できるらしい。
-Dcustom.dics=/abs/path/to/dic1.csv /abs/path/to/dic2.csv
さっそく、dictionary/ipadic/dictionary.csvの記述を参考に、こんな内容が書かれたadd.csvというファイルを作ってみる。
"ランスロット",2770,名詞,固有名詞,人名,姓,*,*,"ランスロット","ランスロット","ランスロット"
"マーリン",2770,名詞,固有名詞,人名,名,*,*,"マーリン","マーリン","マーリン"
上記のCSVを辞書に追加します。
#dictionaryを一度cleanする
$ cd dictionary
$ ant clean-sen
$ cd ../
#引数にフルパスでCSVファイルの場所を指定してantを実行する
$ ant -Ddictype=ipadic -Dcustom.dics=/home/user/workspace/lucene-gosen/add.csv
distに新しくjarが出力されるので、それをクラスパスに入れて形態素解析してみる。
StringTagger tagger = SenFactory.getStringTagger();
List<Token> tokens = new ArrayList<Token>();
tagger.analyze("ランスロットとマーリン", tokens);
System.out.println(tokens);
辞書追加前 : [ラン, スロット, と, マーリン]
↓
辞書追加後 : [ランスロット, と, マーリン]
ちゃんと追加できたようです。
SenFactoryでStringTaggerやStreamTaggerを生成する際に辞書があるディレクトリのパスを引数に指定すると、1つのプログラムの中で複数の辞書を使い分けることができる。
辞書ディレクトリは、ソースをチェックアウトしてbuildすると、dictionaryディレクトリ配下に作成される。例えば以下のようにantコマンドを打てばipadicとnaist-chasenという2つの辞書ディレクトリができる。
$ ant
$ ant -Ddictype=naist-chasen
試しにこの2つの辞書ディレクトリを使って形態素解析してみる。
List<Token> tokens = new ArrayList<Token>();
// ipadicを利用
StringTagger ipaTagger = SenFactory.getStringTagger("dictionary/ipadic");
System.out.println(ipaTagger.analyze("1984年", tokens));
//=> [1984, 年]
// naist-chasenを利用
StringTagger naistTagger = SenFactory.getStringTagger("dictionary/naist-chasen");
System.out.println(naistTagger.analyze("1984年", tokens));
//=> [1, 9, 8, 4, 年]
ちゃんと辞書によって違う結果が返る。
せっかくlucene-gosenという名前になっているわけなので、Luceneで使ってみる。
とりあえず公式サイトからLuceneのcoreのjarをダウンロードする。
Apache Lucene - Overview
http://lucene.apache.org/java/docs/index.html
ダウンロードしたら、lucene-core-3.5.0.jar(バージョンは適宜読み替え)をクラスパスに入れる。
以下はインデックスを作成する際のサンプルコード。Analyzerはlucene-gosenの中に入っているJapaneseAnalyzerを使用する。
// GoSenが用意しているAnalyzer
Analyzer analyzer = new JapaneseAnalyzer(Version.LUCENE_35);
// インデックスディレクトリの設定
Directory dir = new SimpleFSDirectory(new File("index"));
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_35, analyzer);
IndexWriter writer = new IndexWriter(dir, config);
// titleとcontentというフィールドを持つドキュメントをインデックスに追加してみる
Document doc = new Document();
doc.add(new Field("title", "将来の夢", Field.Store.YES, Field.Index.ANALYZED));
doc.add(new Field("content", "生まれ変わってキャーのお腹で眠りたい", Field.Store.YES, Field.Index.ANALYZED));
writer.addDocument(doc);
// しゅーりょー
writer.close();
登録されたインデックスをLukeで見てみると、「将来」「夢」「生まれ変わる」「お腹」「眠る」などの単語が登録されていた。
登録した文章では「生まれ変わって」と表記しているけど、インデックスには表記揺れが吸収されて「生まれ変わる」と登録されている。
次に作成したインデックスから検索を行うコード。
// 検索の準備
IndexReader reader = IndexReader.open(FSDirectory.open(new File("index")), true);
IndexSearcher searcher = new IndexSearcher(reader);
// 検索クエリの準備
Analyzer analyzer = new JapaneseAnalyzer(Version.LUCENE_35);
QueryParser parser = new QueryParser(Version.LUCENE_35, "content", analyzer);
Query query = parser.parse("content:生まれ変わろう");
// 検索の実行
TopScoreDocCollector collector = TopScoreDocCollector.create(1000, false);
searcher.search(query, collector);
// 検索結果(上位10件)の出力
ScoreDoc[] hits = collector.topDocs(0, 10).scoreDocs;
for (ScoreDoc hit : hits) {
Document doc = searcher.doc(hit.doc);
System.out.println(doc.get("title") + " | " + doc.get("content"));
}
上記の例では「content:生まれ変わろう」で検索していますが、揺れが吸収されて「生まれ変わる」が検索がヒットします。
最後にSolrでも使ってみる。これは既にやっている人も多いし情報も割と豊富なので、さらっと。
まず、Solrを落としてくる。
Apache Solr
http://lucene.apache.org/solr/
解凍して、exampleディレクトリにcdして、そこのsolr/libにlucene-gosenのjarを無造作に放り込む。
$ tar xzvf apache-solr-3.5.0.tgz
$ cd apache-solr-3.5.0/example
$ mkdir solr/lib
$ cp ~/Download/lucene-gosen-1.2.1-ipadic.jar solr/lib
次にschema.xmlをさらっと修正します。今回はとりあえず最低限の記述。
$ vi solr/conf/schema.xml
types要素の中に以下を書き足します。
<fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
<analyzer>
<tokenizer class="solr.JapaneseTokenizerFactory"/>
</analyzer>
</fieldType>
フィルタとかの書き方については、lucene-gosenのソースを落とした時に付いてくるexampleディレクトリの中にあるschema.xml.snippetが参考になります。説明はそこに載っているので割愛。
次にfieldのdescriptionのtypeをtext_generalからtext_jaにします。
<field name="description" type="text_general" indexed="true" stored="true"/>
↓
<field name="description" type="text_ja" indexed="true" stored="true"/>
修正したらSolrサーバを立ち上げます。
$ java -jar start.jar
最後に適当なCSVファイルを作って食わせます。
$ cd exampledocs
$ vi sample.csv
id,title,description
1,1984年,今日も二分間憎悪の時間を始めますよー
2,時計仕掛けのオレンジ,とてもハラショーな気持ちになった
3,華氏451度,最初に出てきた女の子がヒロインだと思った
$ java -Durl=http://localhost:8983/solr/update/csv -jar post.jar sample.csv
Schema Browserを見てみる
http://localhost:8983/solr/admin/schema.jsp
FIELD → DESCRIPTIONを見てみると、「とても」「なる」「ヒロイン」などの形態素解析されたインデックスが貼られていることが分かります。
ちなみに「ハラショー」は「ハラ」と「ショー」に分割されてしまいました。仕方ない。
あと、SolrのANALYSISの画面でtypeにtext_jaを入力し、Field Valueに適当な文字を入力すると、どんな風に解析されるのかが見れて面白いです。
最近はKuromojiやIgoなどJavaベースの形態素解析器がけっこう出ています。この辺はたいていMeCabに近い実装になっているので、同じ辞書を用いた場合の解析は、速度の差こそあれ結果は大きく変わることはないようです。KyTeaベースのとか出ないかな。
その中でlucene-gosenは(今回は取り扱ってませんが)Solrの日本語向けフィルタの非常に充実してます。カタカナの長音の表記揺れとか、動詞の表記揺れとか、日本語の表記にありがちな揺れをけっこう拾えてありがたいです。