MahoutのJavaDocを見てたら、org.apache.mahout.common.nlp.NGramsというクラスを見かけた。
試しに使ってみたのだけど、当然ながらこの子は日本語向けではなかった。
日本語で2~3gramを切り出したいのだけど、さて、何を使おうか。
org.apache.mahout.common.nlp.NGramsは、スペースとタブでsplitしている。
下記のように「i am a pen.」というカッコいい英語を分割する場合は、こんなコードになる。
NGrams ngrams = new NGrams("i am a pen.", 2);
List<String> list = ngrams.generateNGramsWithoutLabel();
for( String gram : list )
System.out.println(gram);
結果は下記。
i i i am am am a a a pen.
ちょっとガッカリな結果。
これに日本語を渡した場合はスペースもタブも含まれないことが多いので、ほとんど分割されずに終わる。
MahoutのdependencyにはLuceneも入ってる。この子を使えば記号とかを飛ばした日本語のNGramも簡単に作ってくれそう。
NGramTokenizerを使えば、日本語のNGramの結果が得られる。
StringReader reader = new StringReader("私はペンです。");
// 2gramと3gramを出力する
TokenStream stream = new NGramTokenizer(reader, 2, 3);
while (stream.incrementToken()) {
CharTermAttribute term = stream.getAttribute(CharTermAttribute.class);
System.out.println(term.toString());
}
結果はこんな感じ。
私は はペ ペン ンで です す。 私はペ はペン ペンで ンです です。
良い感じ。
bigramで、記号は含まず、英語はbigramにしなくて良い場合は、StandardAnalyzer + CJKAnalyzerが良いやもしれない。
StringReader reader = new StringReader("私はペンです。");
TokenStream stream = new StandardTokenizer(Version.LUCENE_36, reader);
stream = new CJKBigramFilter(stream);
while (stream.incrementToken()) {
CharTermAttribute term = stream.getAttribute(CharTermAttribute.class);
System.out.println(term.toString());
}
私は はペ ペン ンで です
できれば記号にはご退場頂きたい。
自前で記号を判定して抜いても良いのだけど、なんとなく自前でTokenizer書いてみる。
とりあえずCharTokenizer使って、文字か数字以外は全部splitしてしまう的なTokenizerを書いて。
public class SymbolTokenizer extends CharTokenizer {
public SymbolTokenizer(Version version, Reader input) {
super(version, input);
}
protected boolean isTokenChar(int c) {
return Character.isLetter(c) || Character.isDigit(c);
}
}
これとNGramTokenFilterを組み合わせれば。
StringReader reader = new StringReader("私はペンです。");
TokenStream stream = new SymbolTokenizer(Version.LUCENE_36, reader);
stream = new NGramTokenFilter(stream, 2, 3);
while (stream.incrementToken()) {
CharTermAttribute term = stream.getAttribute(CharTermAttribute.class);
System.out.println(term.toString());
}
文字と数値だけを使ったNGramが好きなサイズで作れそう。
StringReader reader = new StringReader("私はペンです・・・よね?");
TokenStream stream = new SymbolTokenizer(Version.LUCENE_36, reader);
stream = new NGramTokenFilter(stream, 2, 3);
while (stream.incrementToken()) {
CharTermAttribute term = stream.getAttribute(CharTermAttribute.class);
System.out.println(term.toString());
}
結果はこんな感じ。
私は はペ ペン ンで です 私はペ はペン ペンで ンです よね