0
0

More than 1 year has passed since last update.

Lucene の CJKAnalyzer で カタカナだけ Bigram じゃなくする。

Last updated at Posted at 2022-10-11

成果物

CJKAnalyzer とは?

Lucene で 日本語韓国語中国語 を Bi-gram にしてくれるやつ。
ただ、日本語だとカタカナはバイグラムじゃなくても良かったりする。
CJKAnalyzer を使う簡単なソースコードだけ、先にあげときます。

Sample.java
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;

import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;

public class Sample {
	public static void main(String[] args) {
		String text = "岸田文雄首相は衆院本会議で所信表明演説し、個人のリスキリング(学び直し)の支援に5年で1兆円を投じると表明した。";
		CJKAnalyzer analyzer = new CJKAnalyzer();
		// 英語でよく使う StandardAnalyzer では、
		// StandardAnalyzer analyzer = new StandardAnalyzer();
		TokenStream tokenStream = analyzer.tokenStream("testField", text);
		try {
			tokenStream.reset();
			CharTermAttribute termAtt = tokenStream.addAttribute(CharTermAttribute.class);
			// OffsetAttribute offsetAtt = tokenStream.addAttribute(OffsetAttribute.class);
			while (tokenStream.incrementToken()) {
				System.out.println(termAtt.toString());
			}
			tokenStream.end();
		} catch (IOException e) {
			System.out.println(e);
		}
	}
}

lucene/lucene/analysis/common/src/java/org/apache/lucene/analysis/cjk にある cjk と util を /lucene/lucene/core/src/java/org/apache/lucene/analysis に移動させて、上の Sample.java を lucene/lucene/core/src/java に置けば動きます。

結果は、Bi-gram された結果が出てきたのではないでしょうか?

カタカナだけ Bigram でなくする。

日本語では特に Bigram にする必要もない カタカナだけ、連続して表示させてみましょう。
まずは、CJKAnalyzer.java のソースコード を見ます。一番下の方です。

CJKAnalyzer.java
  @Override
  protected TokenStreamComponents createComponents(String fieldName) {
    final Tokenizer source = new StandardTokenizer();
    // run the widthfilter first before bigramming, it sometimes combines characters.
    TokenStream result = new CJKWidthFilter(source);
    result = new LowerCaseFilter(result);
    result = new CJKBigramFilter(result);
    return new TokenStreamComponents(source, new StopFilter(result, stopwords));
  }

ここの StandardTokenizer で 日本語を 1文字区切りにしていますが、これをカタカナだけ一まとまりにしてくれたら、カタカナだけ Bi-gram にしないようにできそうじゃないでしょうか?
なので、この StandardTokenizer の中身を見てみましょう。
詳細は、この記事 に譲るとして、今回は この JFlex ファイルをいじれば、StandardTokenizer (元ファイル) の結果だけでは、カタカナを一まとまりにすることができます。それが下のコードです。

StandardTokenizerImpl.jflex の 90行目
KatakanaEx          = [\p{WB:Katakana}]

[] をつけただけですが、これで StandardTokenizer はカタカナを1まとまりにしてはき出してくれます。
ですが、このまま CJKAnalyzer を使っても、カタカナは Bi-gram のまま表示されます。ここで見ておく必要が出てくるのが、CJKBigramFilter です。

今回は、結果だけ載せますが、

CJKBigramFilter.java
public static final String EXTRA_TYPE = "<EXTRA>";
// 省略
  private void flushExtragram(int extra_length) {
    //
    refill();
    // 【 や 】でズレた時 用の patch
    char bufferChar = (char)buffer[index + 1];
    int bufferOffset = 0;
    if (bufferChar != originalBuffer[0]) {
      int[] checkBufferRange = Arrays.copyOfRange(buffer, index + 1, index + extraLength + 1);
      char[] checkOriginalBufferRange = Arrays.copyOfRange(originalBuffer, index, index + extraLength);
      for (int i = 0; i < checkOriginalBufferRange.length; i++) {
        if ( checkOriginalBufferRange[i] == (char)checkBufferRange[0] ) {
          bufferOffset = i;
          break;
        }
      }
    }
    int originalIndex = 1;
    int originalExtraLength = extraLength + 1;
    originalIndex -= bufferOffset;
    originalExtraLength -= bufferOffset;
    //
    clearAttributes();
    char[] termBuffer = termAtt.resizeBuffer(extraLength*2);
    int len_extra = 0;
    for (int i = originalIndex; i < originalExtraLength; i++){
      int len_i = Character.toChars(buffer[index + i], termBuffer, len_extra);
      len_extra += len_i;
    }
    termAtt.setLength(len_extra);
    offsetAtt.setOffset(startOffset[originalIndex], endOffset[originalIndex + originalExtraLength - bufferOffset]);
    typeAtt.setType(EXTRA_TYPE);
    index += extraLength + 1 - bufferOffset;
  }

の定数と関数が新しく追加した部分で、この関数を

CJKBigramFilter.java
      } else if (doNext()) {

        // case 2: look at the token type. should we form any n-grams?

        String type = typeAtt.type();
        if (type == doHan || type == doHiragana || type == doKatakana || type == doHangul) {
+          if (termAtt.length() > 1) {
+            //
+            flushExtragram(termAtt.length());
+            return true;
+          }

188行目あたりで呼び出せば、カタカナだけ連続して表示されるはずです。
簡単に説明すると、doNext() で StandardTokenizer で分割された 次の トークン を読み出す時に、その長さが 1 より大きい場合は カタカナになるので、その場合だけ flushExtragram という関数で独自に termAtt に一まとまりのカタカナを登録する流れになります。
最後の index += extra_length + 1 - bufferOffset で トークンにあるカタカナの数だけ index を飛ばしています。

StandardTokenizer で出来たから試したけど、意外とできた。
修正点などあったら、ご指摘お願いします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0