【Lucene】CJKAnalyzerを使用した日本語対応

【Lucene】CJKAnalyzerを使用した日本語対応

Analyzerの基本動作のページでStandardAnalyzerを元にAnalyzerの基本動作の解説を行っていますが、日本語を正しく扱うことができていません。

ここでは日本語に対応可能なAnalyzerとなるCJKAnalyzerを使用したサンプルプログラムを元に、その動作確認とより詳しいAnalyzerの解説を行っていきます。

まずはCJKAnalyzerの動作を基本処理 インデックスの登録のページ基本処理 インデックスの確認のページで紹介したサンプルプログラムを元に確認してみます。

プログラムの主な変更箇所を中心に以下に示します。

import java.io.File, import java.util.Iterator, import java.util.List, import org.apache.lucene.analysis.Analyzer, import org.apache.lucene.analysis.cjk.CJKAnalyzer, import org.apache.lucene.document.Document, import org.apache.lucene.document.Field, import org.apache.lucene.document.TextField, import org.apache.lucene.index.AtomicReader, import org.apache.lucene.index.AtomicReaderContext, import org.apache.lucene.index.DirectoryReader, import org.apache.lucene.index.Fields, import org.apache.lucene.index.IndexReader, import org.apache.lucene.index.IndexWriter, import org.apache.lucene.index.IndexWriterConfig, import org.apache.lucene.index.IndexWriterConfig.OpenMode, import org.apache.lucene.index.Terms, import org.apache.lucene.index.TermsEnum, import org.apache.lucene.store.Directory, import org.apache.lucene.store.FSDirectory, import org.apache.lucene.util.BytesRef, import org.apache.lucene.util.Version, public class WriteIndex { public static void main(String[] args) { try { WriteIndex wi = new WriteIndex(), wi.main(), } catch (Exception e) { e.printStackTrace(), } } private void main() throws Exception { //(略) try { //(略) analyzer = new CJKAnalyzer(Version.LUCENE_46),//---(1) IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_46, analyzer), config.setOpenMode(OpenMode.CREATE), writer = new IndexWriter(dir, config), Document doc = null, doc = new Document(),//---(2) doc.add(new TextField('txt1', 'あいう アイウ アイウ', Field.Store.YES)), writer.addDocument(doc), doc = new Document(),//---(3) doc.add(new TextField('txt1', 'がぎぐ ガギグ ガギグ', Field.Store.YES)), writer.addDocument(doc), doc = new Document(),//---(4) doc.add(new TextField('txt1', '123 123 一二三', Field.Store.YES)), writer.addDocument(doc), doc = new Document(),//---(5) doc.add(new TextField('txt1', 'abc abc ABC ABC', Field.Store.YES)), writer.addDocument(doc), doc = new Document(),//---(6) doc.add(new TextField('txt1', 'This is a pen', Field.Store.YES)), writer.addDocument(doc), writer.commit(), //登録したインデックスの確認 checkIndex(writer), } finally { //(略) } } private void checkIndex(IndexWriter writer) throws Exception { //(略) } }

上記プログラムの解説を行います。

(1)

AnalyzerにCJKAnalyzerを使用しています。

コンストラクタでは使用するLuceneのバージョンを指定しています。

このサンプルプログラムの実行結果は以下のようになります。

----- fieldName:txt1 ----- fieldText:123 fieldText:abc fieldText:pen fieldText:あい fieldText:いう fieldText:がぎ fieldText:ぎぐ fieldText:アイ fieldText:イウ fieldText:ガギ fieldText:ギグ fieldText:一二 fieldText:二三

(2)でDocumentのテキストデータとして「あいう アイウ アイウ」と指定したものは、「あい」、「いう」、「アイ」、「イウ」に分割されました。

まずCJKAnalyzerの大きな特徴としてCJKBigramFilterが使われているのですが、これにより日本語文字は2文字ずつ連続したものに分割されます。

文字列をn文字ずつ連続したものに分割することはnグラムと呼ばれているのですが、なかでも2文字ずつ連続したものに分割することはバイグラムと呼ばれています。

そしてCJKAnalyzerではCJKWidthFilterが使われているのですが、これにより半角カナは全角カナに変換が行われます。

よって「アイウ」と「アイウ」は、「アイ」、「イウ」への分割に統一されています。

(3)でDocumentのテキストデータとして「がぎぐ ガギグ ガギグ」と指定したものは、「がぎ」、「ぎぐ」、「ガギ」、「ギグ」に分割されました。

CJKWidthFilterでは濁音を含む半角カナも全角カナへの変換が正しく行われています。

(4)でDocumentのテキストデータとして「123 123 一二三」と指定したものは、「123」、「一二」、「二三」に分割されました。

CJKWidthFilterでは全角数字は半角数字に変換が行われています。

また半角数字はCJKBigramFilterによる分割の対応外となっています。

(5)でDocumentのテキストデータとして「abc abc ABC ABC」と指定したものは、「abc」のみに分割されました。

CJKWidthFilterにより全角英字(「abc」、「ABC」)は半角英字(「abc」、「ABC」)となり、CJKAnalyzerではLowerCaseFilterが使われていますので、大文字(「ABC」)は小文字(「abc」)に変換が行われるため、このような結果となっています。

また半角英字はCJKBigramFilterによる分割の対応外となっています。

(6)でDocumentのテキストデータとして「This is a pen」と指定したものは、「pen」のみに分割されました。

CJKAnalyzerではStopFilterが使われていますので、「This」、「is」、「a」は除外されています。

ここでCJKAnalyzerを元にAnalyzerのより詳しい解説を行います。

CJKAnalyzerで「あいう」というテキストデータが「あい」、「いう」に分割されるときのTokenizerとTokenFilterの動作は以下のようになっています。

尚、CJKAnalyzerではStandardTokenizer、CJKWidthFilter、LowerCaseFilter、CJKBigramFilter、StopFilterの順に生成が行われています。

またStandardTokenizerでは、「あいう」は「あ」、「い」、「う」に分割されるようになっています。

ここで使用している用語の「トークン」は、Tokenizerによりテキストデータを分割した結果を表します。

IndexWriterによるDocument追加処理 ↓Analyzerを使用して次のトークンを取得 StopFilter ↓次のトークンを取得 CJKBigramFilter ↓次のトークンを取得 LowerCaseFilter ↓次のトークンを取得 CJKWidthFilter ↓次のトークンを取得 StandardTokenizer ↓トークンに「あ」を設定 CJKWidthFilter ↓トークンに全角⇔半角の変換を行った結果「あ」を設定 LowerCaseFilter ↓トークンに大文字→小文字の変換を行った結果「あ」を設定 CJKBigramFilter ↓「あ」のみでは2文字に満たないので、次のトークンを取得 LowerCaseFilter ↓次のトークンを取得 CJKWidthFilter ↓次のトークンを取得 StandardTokenizer ↓トークンに「い」を設定 CJKWidthFilter ↓トークンに全角⇔半角の変換を行った結果「い」を設定 LowerCaseFilter ↓トークンに大文字→小文字の変換を行った結果「い」を設定 CJKBigramFilter ↓トークンに前回の「あ」と合わせて2文字とした結果「あい」を設定 StopFilter ↓トークンに対象外とするキーワードを除いた結果「あい」を設定 IndexWriterによるDocument追加処理 ↓Analyzerを使用して次のトークンを取得 StopFilter ↓次のトークンを取得 CJKBigramFilter ↓次のトークンを取得 LowerCaseFilter ↓次のトークンを取得 CJKWidthFilter ↓次のトークンを取得 StandardTokenizer ↓トークンに「う」を設定 CJKWidthFilter ↓トークンに全角⇔半角の変換を行った結果「う」を設定 LowerCaseFilter ↓トークンに大文字→小文字の変換を行った結果「う」を設定 CJKBigramFilter ↓トークンに前回の「い」と合わせて2文字とした結果「いう」を設定 StopFilter ↓トークンに対象外とするキーワードを除いた結果「いう」を設定 IndexWriterによるDocument追加処理 Analyzerによるトークンの取得終了 

もうひとつ例をあげますと、CJKAnalyzerで「アイウ」というテキストデータが「アイ」、「イウ」に分割されるときのTokenizerとTokenFilterの動作は以下のようになっています。

尚、StandardTokenizerでは、「アイウ」は分割されず「アイウ」のままになります。

IndexWriterによるDocument追加処理 ↓Analyzerを使用して次のトークンを取得 StopFilter ↓次のトークンを取得 CJKBigramFilter ↓次のトークンを取得 LowerCaseFilter ↓次のトークンを取得 CJKWidthFilter ↓次のトークンを取得 StandardTokenizer ↓トークンに「アイウ」を設定 CJKWidthFilter ↓トークンに全角⇔半角の変換を行った結果「アイウ」を設定 LowerCaseFilter ↓トークンに大文字→小文字の変換を行った結果「アイウ」を設定 CJKBigramFilter ↓トークンに「アイウ」から最初の2文字を取得した結果「アイ」を設定 StopFilter ↓トークンに対象外とするキーワードを除いた結果「アイ」を設定 IndexWriterによるDocument追加処理 ↓Analyzerを使用して次のトークンを取得 StopFilter ↓次のトークンを取得 CJKBigramFilter ↓トークンに「アイウ」から次の2文字を取得した結果「イウ」を設定 StopFilter ↓トークンに対象外とするキーワードを除いた結果「イウ」を設定 IndexWriterによるDocument追加処理 Analyzerによるトークンの取得終了 

このようにAnalyzerで生成したTokenizerを元にトークンを取得し、TokenFilterでフィルターを行う処理が繰り返されるようになっており、nグラム方式のフィルターでは対象の文字数のトークンになるよう制御が行われています。

CJKAnalyzerにより日本語も正しく扱えるようになっているのですが、バイグラム方式では1文字での検索を行うことができません。この対処方法は別途解説を行います。