改善すべき点
NLP4J [006-034] NLP4J で言語処理100本ノック #34 「AのB」 では「AのB」を抽出する処理を直接コードで書いていました。以下の部分です。
// 「AのB」を探す
String meishi_a = null;
String no = null;
for (Keyword kwd : kwds) {
if (meishi_a == null && kwd.getFacet().equals("名詞")) {
meishi_a = kwd.getLex();
} //
else if (meishi_a != null && no == null && kwd.getLex().equals("の")) {
no = kwd.getLex();
} //
else if (meishi_a != null && no != null && kwd.getFacet().equals("名詞")) {
System.err.println(meishi_a + no + kwd.getLex());
meishi_a = null;
no = null;
} //
else {
meishi_a = null;
no = null;
}
}
このようなキーワード抽出(Annotation)の方法ではロジックの再利用ができません。
Annotator
そこでNLP4Jでは独自にAnnotationを追加できる仕組みであるAnnotatorを用意しています。
仕組みといっても単純で、Interface nlp4j.DocumentAnnotator を実装することです。
上記のロジックをAnnotatorのコードとして用意すると以下のようになります。
「AのB」を単純に文字列として出力して終わりではなく、新しいキーワードとして追加しています。
キーワードの種類を識別できるように「word_nn_no_nn」という識別子(=ファセット: facet)を設定しています。
package nlp4j.annotator;
import java.util.ArrayList;
import nlp4j.AbstractDocumentAnnotator;
import nlp4j.Document;
import nlp4j.DocumentAnnotator;
import nlp4j.Keyword;
import nlp4j.impl.DefaultKeyword;
/**
* 「名詞の名詞」を「word_nn_no_nn」キーワードとして抽出します。
* @author Hiroki Oya
*/
public class Nokku34Annotator extends AbstractDocumentAnnotator implements DocumentAnnotator {
@Override
public void annotate(Document doc) throws Exception {
ArrayList<Keyword> newkwds = new ArrayList<>();
Keyword meishi_a = null;
Keyword no = null;
for (Keyword kwd : doc.getKeywords()) {
if (meishi_a == null && kwd.getFacet().equals("名詞")) {
meishi_a = kwd;
} //
else if (meishi_a != null && no == null && kwd.getLex().equals("の")) {
no = kwd;
} //
else if (meishi_a != null && no != null && kwd.getFacet().equals("名詞")) {
Keyword kw = new DefaultKeyword();
kwd.setLex(meishi_a.getLex() + no.getLex() + kwd.getLex());
kwd.setFacet("word_nn_no_nn");
kwd.setBegin(meishi_a.getBegin());
kwd.setEnd(kwd.getEnd());
kwd.setStr(meishi_a.getStr() + no.getStr() + kwd.getStr());
kwd.setReading(meishi_a.getReading() + no.getReading() + kwd.getReading());
newkwds.add(kw);
meishi_a = null;
no = null;
} //
else {
meishi_a = null;
no = null;
}
}
doc.addKeywords(newkwds);
}
}
これで「AのB」というキーワードを抽出するロジックを切り分けて定義することができました。
Annotator の利用
package nlp4j.nokku.chap4;
import java.util.List;
import nlp4j.Document;
import nlp4j.DocumentAnnotator;
import nlp4j.DocumentAnnotatorPipeline;
import nlp4j.Keyword;
import nlp4j.crawler.Crawler;
import nlp4j.crawler.TextFileLineSeparatedCrawler;
import nlp4j.impl.DefaultDocumentAnnotatorPipeline;
import nlp4j.annotator.Nokku34Annotator;
public class Nokku34b {
public static void main(String[] args) throws Exception {
// NLP4Jが提供するテキストファイルのクローラーを利用する
Crawler crawler = new TextFileLineSeparatedCrawler();
crawler.setProperty("file", "src/test/resources/nlp4j.crawler/neko_short_utf8.txt");
crawler.setProperty("encoding", "UTF-8");
crawler.setProperty("target", "text");
// ドキュメントのクロール
List<Document> docs = crawler.crawlDocuments();
// NLPパイプライン(複数の処理をパイプラインとして連結することで処理する)の定義
DocumentAnnotatorPipeline pipeline = new DefaultDocumentAnnotatorPipeline();
{
// Yahoo! Japan の形態素解析APIを利用するアノテーター
DocumentAnnotator annotator = new YJpMaAnnotator();
pipeline.add(annotator);
}
{
// 「名詞の名詞」を「word_nn_no_nn」キーワードとして抽出します。
Nokku34Annotator annotator = new Nokku34Annotator(); // ←課題34はここだけ
pipeline.add(annotator); // ←課題34はここだけ
}
// アノテーション処理の実行
pipeline.annotate(docs);
for (Document doc : docs) {
for (Keyword kwd : doc.getKeywords("word_nn_no_nn")) {
System.err.println(kwd.getStr());
}
}
}
}
「AのB」を抽出する処理がたった2行になりました!
// 「名詞の名詞」を「word_nn_no_nn」キーワードとして抽出します。
Nokku34Annotator annotator = new Nokku34Annotator();
pipeline.add(annotator);
このようにすれば、独自のAnnotatorをたくさん定義して、さらに自然言語処理を拡張することができるようになるのです。
結果
彼の掌
掌の上
書生の顔
はずの顔
顔の真中
穴の中
まとめ
NLP4J を使うと、Javaで簡単に自然言語処理ができますね!
#プロジェクトURL
https://www.nlp4j.org/