LUCENE-3730: improve Kuromoji search mode heuristics

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1239061 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Robert Muir 2012-02-01 11:03:17 +00:00
parent 2f159c4059
commit 995c5b9ef1
5 changed files with 239 additions and 22 deletions

View File

@ -142,6 +142,9 @@ New Features
* LUCENE-3305: Added Kuromoji morphological analyzer for Japanese.
(Christian Moen, Masaru Hasegawa, Simon Willnauer, Uwe Schindler, Mike McCandless, Robert Muir)
* LUCENE-3730: Refine Kuromoji search mode (Mode.SEARCH) decompounding
heuristics. (Christian Moen via Robert Muir)
* LUCENE-3685: Add ToChildBlockJoinQuery and renamed previous
BlockJoinQuery to ToParentBlockJoinQuery, so that you can now do
joins in both parent to child and child to parent directions.

View File

@ -54,11 +54,13 @@ public class Viterbi {
private static final int DEFAULT_COST = 10000000;
private static final int SEARCH_MODE_LENGTH_KANJI = 3;
private static final int SEARCH_MODE_KANJI_LENGTH = 2;
private static final int SEARCH_MODE_LENGTH = 7;
private static final int SEARCH_MODE_OTHER_LENGTH = 7; // Must be >= SEARCH_MODE_KANJI_LENGTH
private static final int SEARCH_MODE_PENALTY = 10000;
private static final int SEARCH_MODE_KANJI_PENALTY = 3000;
private static final int SEARCH_MODE_OTHER_PENALTY = 1700;
private static final char[] BOS = "BOS".toCharArray();
@ -137,7 +139,7 @@ public class Viterbi {
char[] surfaceForm = node.getSurfaceForm();
int offset = node.getOffset();
int length = node.getLength();
if (length > SEARCH_MODE_LENGTH_KANJI) {
if (length > SEARCH_MODE_KANJI_LENGTH) {
boolean allKanji = true;
// check if node consists of only kanji
for (int pos = 0; pos < length; pos++) {
@ -148,9 +150,9 @@ public class Viterbi {
}
if (allKanji) { // Process only Kanji keywords
pathCost += (length - SEARCH_MODE_LENGTH_KANJI) * SEARCH_MODE_PENALTY;
} else if (length > SEARCH_MODE_LENGTH) {
pathCost += (length - SEARCH_MODE_LENGTH) * SEARCH_MODE_PENALTY;
pathCost += (length - SEARCH_MODE_KANJI_LENGTH) * SEARCH_MODE_KANJI_PENALTY;
} else if (length > SEARCH_MODE_OTHER_LENGTH) {
pathCost += (length - SEARCH_MODE_OTHER_LENGTH) * SEARCH_MODE_OTHER_PENALTY;
}
}
}

View File

@ -1,18 +1,5 @@
package org.apache.lucene.analysis.kuromoji;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.kuromoji.Segmenter.Mode;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.util.UnicodeUtil;
import org.apache.lucene.util._TestUtil;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@ -30,6 +17,19 @@ import org.apache.lucene.util._TestUtil;
* limitations under the License.
*/
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.kuromoji.Segmenter.Mode;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.util.UnicodeUtil;
import org.apache.lucene.util._TestUtil;
public class TestExtendedMode extends BaseTokenStreamTestCase {
private final Segmenter segmenter = new Segmenter(Mode.EXTENDED);
private final Analyzer analyzer = new Analyzer() {

View File

@ -0,0 +1,72 @@
package org.apache.lucene.analysis.kuromoji;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.kuromoji.Segmenter.Mode;
import org.apache.lucene.util.IOUtils;
public class TestSearchMode extends BaseTokenStreamTestCase {
private final static String SEGMENTATION_FILENAME = "search-segmentation-tests.txt";
private final Segmenter segmenter = new Segmenter(Mode.SEARCH);
private final Analyzer analyzer = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
Tokenizer tokenizer = new KuromojiTokenizer(segmenter, reader);
return new TokenStreamComponents(tokenizer, tokenizer);
}
};
/** Test search mode segmentation */
public void testSearchSegmentation() throws IOException {
InputStream is = TestSearchMode.class.getResourceAsStream(SEGMENTATION_FILENAME);
if (is == null) {
throw new FileNotFoundException("Cannot find " + SEGMENTATION_FILENAME + " in test classpath");
}
try {
LineNumberReader reader = new LineNumberReader(new InputStreamReader(is, IOUtils.CHARSET_UTF_8));
String line = null;
while ((line = reader.readLine()) != null) {
// Remove comments
line = line.replaceAll("#.*$", "");
// Skip empty lines or comment lines
if (line.trim().isEmpty()) {
continue;
}
if (VERBOSE) {
System.out.println("Line no. " + reader.getLineNumber() + ": " + line);
}
String[] fields = line.split("\t", 2);
String sourceText = fields[0];
String[] expectedTokens = fields[1].split("\\s+");
assertAnalyzesTo(analyzer, sourceText, expectedTokens);
}
} finally {
is.close();
}
}
}

View File

@ -0,0 +1,140 @@
###
### Tests for Kuromoji's search mode heuristic
###
### In search-mode, Kuromoji uses a heuristic to do extra splitting of words
### to get a decompounding effect useful for search. This file includes tests
### for this heuristic and demonstrates its usefulness, but also weaknesses.
###
### This file's format is as follows:
### <text><tab><token1> <token2> ... <token>
###
### This file should use UTF-8 encoding and there is one test per line. The
### text to be segmented and its expected surface form token sequence is
### separated by a tab ('\t'). Tokens are separated by a half-width space.
### Whitespace lines and lines starting with a '#' are ignored. Comments
### are not allowed on entry line.
###
### NOTE: These tests depends on IPADIC
###
### Revision history:
### - 2012-01-29: Initial version
###
##
## Organizations
##
# Kansai Internationl Airport
関西国際空港 関西 国際 空港
# Narita Airport
成田空港 成田 空港
# Haneda Airport
羽田空港 羽田 空港
# Nara Institute of Science and Technology
奈良先端科学技術大学院大学 奈良 先端 科学 技術 大学院 大学
# Tokyo University
東京大学 東京 大学
# Kyoto University
京都大学 京都 大学
# Kyoto University Baseball Club
京都大学硬式野球部 京都 大学 硬式 野球 部
##
## Katakana titles
##
# Senior Software Engineer
シニアソフトウェアエンジニア シニア ソフトウェア エンジニア
# Software Engineer
ソフトウェアエンジニア ソフトウェア エンジニア
# Senior Project Manager
シニアプロジェクトマネジャー シニア プロジェクト マネジャー
# Project Manager
プロジェクトマネジャー プロジェクト マネジャー
# Senior Sales Engineer
シニアセールスエンジニア シニア セールス エンジニア
# System Architect
システムアーキテクト システム アーキテクト
# Senior System Architect
シニアシステムアーキテクト シニア システム アーキテクト
# System Administrator
システムアドミニストレータ システム アドミニストレータ
システムアドミニストレーター システム アドミニストレーター
# Senior System Administrator
シニアシステムアドミニストレーター シニア システム アドミニストレーター
##
## Company names (several are fictitious)
##
# SoftBank Mobile
ソフトバンクモバイル ソフトバンク モバイル
# Alpine Materials
アルパインマテリアルズ アルパイン マテリアルズ
# Sapporo Holdings
サッポロホールディングス サッポロ ホールディングス
# Yamada Corporation
ヤマダコーポレーション ヤマダ コーポレーション
# Canon Semiconductor equipement NOTE: Semiconductor becomes semi + conductor
キヤノンセミコンダクターエクィップメント キヤノン セミ コンダクター エクィップメント
# Orental Chain
オリエンタルチエン オリエンタル チエン
# Ally Projects Japan NOTE: Becomes one token as プロジェクツ is not in IPADIC
アーリープロジェクツジャパン アーリープロジェクツジャパン
# Peter Pan Corporation
ピーターパンコーポレーション ピーター パン コーポレーション
# AIM Create
エイムクリエイツ エイムクリエイツ
# Mars Engineering
マースエンジニアリング マース エンジニアリング
# Fuji Protein Technology
フジプロテインテクノロジー フジ プロテイン テクノロジー
##
## Person names
##
# Michael Jackson
マイケルジャクソン マイケル ジャクソン
# Steve Jobs
スティーブジョブズ スティーブ ジョブズ
# Harry Potter NOTE: Becomes one token (short word)
ハリーポッター ハリーポッター
# Bill Gates NOTE: Becomes one token (short work)
ビルゲイツ ビルゲイツ
# Sean Connery NOTE: Becomes one token (okay)
ショーンコネリー ショーンコネリー
##
## Other nouns
##
# Holdings
ホールディングス ホールディングス
# Engineering
エンジニアリング エンジニアリング
# Software Engineering
ソフトウェアエンジニアリング ソフトウェア エンジニアリング
# Shopping center
ショッピングセンター ショッピング センター
# Game center (arcade) NOTE: One token because of short word
ゲームセンター ゲームセンター
# Christmas shopping
クリスマスショッピング クリスマス ショッピング
# Download file
ダウンロードファイル ダウンロード ファイル
# Technology
テクノロジー テクノロジー
# Lillehammer Olympics
リレハンメルオリンピック リレハンメル オリンピック
##
## Problematic terms
##
# JT Engineering NOTE: Becomes J Tien ginia ring (substrings are in IPADIC)
ジェイティエンジニアリング ジェイ ティエン ジニア リング
# Anchovy pasta NOTE: Become Anch yvipasta
アンチョビパスタ アンチ ョビパスタ
# Surprise gift NOTE: Becomes one token (surprise not in IPADIC)
サプライズギフト サプライズギフト