1概要

Apache OpenNLPは、オープンソースのNatural Language Processing Javaライブラリです。

これは、名前付きエンティティ認識、文検出、POSタグ付け、トークン化などのユースケース用のAPIを備えています。

このチュートリアルでは、さまざまなユースケースにこのAPIを使用する方法について説明します。


2 Mavenのセットアップ

まず、

pom.xml

に主な依存関係を追加する必要があります。

<dependency>
    <groupId>org.apache.opennlp</groupId>
    <artifactId>opennlp-tools</artifactId>
    <version>1.8.4</version>
</dependency>

最新の安定版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22opennlp-tools%22[Maven Central]にあります。

ユースケースによっては訓練されたモデルが必要です。定義済みのモデルhttp://opennlp.sourceforge.net/models-1.5/[here]およびこれらのモデルに関する詳細情報http://opennlp.apache.org/models.html[here]をダウンロードできます。


3文の検出

文とは何かを理解することから始めましょう。

文検出を実装するために、モデルをロードしてのインスタンスに渡します。

@Test
public void givenEnglishModel__whenDetect__thenSentencesAreDetected()
  throws Exception {

    String paragraph = "This is a statement. This is another statement."
      + "Now is an abstract word for time, "
      + "that is always flying. And my email address is[email protected]";

    InputStream is = getClass().getResourceAsStream("/models/en-sent.bin");
    SentenceModel model = new SentenceModel(is);

    SentenceDetectorME sdetector = new SentenceDetectorME(model);

    String sentences[]= sdetector.sentDetect(paragraph);
    assertThat(sentences).contains(
      "This is a statement.",
      "This is another statement.",
      "Now is an abstract word for time, that is always flying.",
      "And my email address is[email protected]");
}

注:




サフィックス「ME」は、Apache OpenNLPの多くのクラス名で使用されており、「最大エントロピー」に基づくアルゴリズムを表します。


4トークン化

テキストのコーパスを文に分割できるようになったので、文の分析をさらに詳しく始めることができます。

トークン化の目的は、文をトークンと呼ばれる小さな部分に分割することです。通常、これらのトークンは単語、数字または句読点です。

OpenNLPには3種類のトークナイザーがあります。


4.1.

TokenizerME


を使用する

この場合、最初にモデルをロードする必要があります。モデルファイルはhttp://opennlp.sourceforge.net/models-1.5/[こちら]からダウンロードし、

/resources

フォルダに置いてそこからロードできます。

次に、ロードしたモデルを使用して

TokenizerME

のインスタンスを作成し、

tokenize()

メソッドを使用して任意の__Stringに対してトークン化を実行します。

@Test
public void givenEnglishModel__whenTokenize__thenTokensAreDetected()
  throws Exception {

    InputStream inputStream = getClass()
      .getResourceAsStream("/models/en-token.bin");
    TokenizerModel model = new TokenizerModel(inputStream);
    TokenizerME tokenizer = new TokenizerME(model);
    String[]tokens = tokenizer.tokenize("Baeldung is a Spring Resource.");

    assertThat(tokens).contains(
      "Baeldung", "is", "a", "Spring", "Resource", ".");
}

ご覧のとおり、トークナイザはすべての単語とピリオド文字を別々のトークンとして識別しています。このトークナイザはカスタムトレーニングモデルでも使用できます。


4.2.

ホワイトスペーストークナイザー


名前が示すように、このトークナイザは空白文字を区切り文字として使用して文をトークンに分割します。

@Test
public void givenWhitespaceTokenizer__whenTokenize__thenTokensAreDetected()
  throws Exception {

    WhitespaceTokenizer tokenizer = WhitespaceTokenizer.INSTANCE;
    String[]tokens = tokenizer.tokenize("Baeldung is a Spring Resource.");

    assertThat(tokens)
      .contains("Baeldung", "is", "a", "Spring", "Resource.");
  }

文は空白で区切られているので、「リソース」という単語と「ピリオド」という2つのトークンではなく、「末尾にピリオド」を付けた「リソース」を1つのトークンとして取得できます。


4.3.

SimpleTokenizer


このトークナイザは

WhitespaceTokenizer

よりも少し洗練されており、文を単語、数字、句読点に分割します。これはデフォルトの動作であり、モデルを必要としません。

@Test
public void givenSimpleTokenizer__whenTokenize__thenTokensAreDetected()
  throws Exception {

    SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
    String[]tokens = tokenizer
      .tokenize("Baeldung is a Spring Resource.");

    assertThat(tokens)
      .contains("Baeldung", "is", "a", "Spring", "Resource", ".");
  }


5名前付きエンティティの認識

トークン化を理解したので、成功したトークン化に基づく最初のユースケースを見てみましょう:名前付きエンティティ認識(NER)。

  • NERの目的は、人、場所、組織、その他の名前付きのものなどの名前付きエンティティを特定のテキストで見つけることです。

OpenNLPは、個人名、日付と時刻、場所、および組織に対して事前定義されたモデルを使用します。

TokenNameFinderModel

を使用してモデルをロードし、それを

NameFinderMEのインスタンスに渡す必要があります。次に、

find()__メソッドを使用して、指定されたテキスト内の名前付きエンティティを検索できます。

@Test
public void
  givenEnglishPersonModel__whenNER__thenPersonsAreDetected()
  throws Exception {

    SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
    String[]tokens = tokenizer
      .tokenize("John is 26 years old. His best friend's "
        + "name is Leonard. He has a sister named Penny.");

    InputStream inputStreamNameFinder = getClass()
      .getResourceAsStream("/models/en-ner-person.bin");
    TokenNameFinderModel model = new TokenNameFinderModel(
      inputStreamNameFinder);
    NameFinderME nameFinderME = new NameFinderME(model);
    List<Span> spans = Arrays.asList(nameFinderME.find(tokens));

    assertThat(spans.toString())
      .isEqualTo("[[0..1) person,[13..14) person,[20..21) person]");
}

アサーションで見ることができるように、結果はテキストの名前付き実体を構成するトークンの開始と終了インデックスを含む

Span

オブジェクトのリストです。

6.品詞タグ付け


  • NN –

    名詞、単数形、またはマス


  • DT –

    決定子


  • VB –

    動詞、基本形


  • VBD –

    動詞、過去形


  • VBZ –

    動詞、三人称単数形のプレゼント


  • IN –

    前置詞または従属接続詞


  • NNP –

    固有名詞、単数形


  • TO –

    「to」という言葉


  • JJ –

    形容詞

これらはPenn Tree Bankで定義されているものと同じタグです。完全なリストはhttps://www.ling.upenn.edu/courses/Fall

2003/ling001/penn

treebank__pos.html[このリスト]を参照してください。

NERの例と同様に、適切なモデルをロードしてから、トークンのセットに対して

POSTaggerME

とそのメソッド

tag()

を使用して文にタグを付けます。

@Test
public void givenPOSModel__whenPOSTagging__thenPOSAreDetected()
  throws Exception {

    SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
    String[]tokens = tokenizer.tokenize("John has a sister named Penny.");

    InputStream inputStreamPOSTagger = getClass()
      .getResourceAsStream("/models/en-pos-maxent.bin");
    POSModel posModel = new POSModel(inputStreamPOSTagger);
    POSTaggerME posTagger = new POSTaggerME(posModel);
    String tags[]= posTagger.tag(tokens);

    assertThat(tags).contains("NNP", "VBZ", "DT", "NN", "VBN", "NNP", ".");
}


tag()

メソッドはトークンをPOSタグのリストにマッピングします。例の結果は次のとおりです。

  1. “ John” – NNP(固有名詞)

  2. 「持っている」 – VBZ(動詞)

  3. “ a” – DT(デターミナ)

  4. “姉妹” – NN(名詞)

  5. 「名前付き」 – VBZ(動詞)

  6. “ Penny” –




    NNP(固有名詞)

  7. “. ” – ピリオド

7.補題

トークンの品詞情報がセンテンスに入ったので、テキストをさらに分析できます。

**

**

統計的な語彙化のためには、モデルを訓練する必要がありますが、辞書の語彙化のためには、https://raw.githubusercontent.com/richardwilly98/elasticsearch-opennlp-auto-tagging/master/src/main/resourcesのような辞書ファイルが必要です。/models/en-lemmatizer.dict[これ。]

辞書ファイルを使用したコード例を見てみましょう。

@Test
public void givenEnglishDictionary__whenLemmatize__thenLemmasAreDetected()
  throws Exception {

    SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
    String[]tokens = tokenizer.tokenize("John has a sister named Penny.");

    InputStream inputStreamPOSTagger = getClass()
      .getResourceAsStream("/models/en-pos-maxent.bin");
    POSModel posModel = new POSModel(inputStreamPOSTagger);
    POSTaggerME posTagger = new POSTaggerME(posModel);
    String tags[]= posTagger.tag(tokens);
    InputStream dictLemmatizer = getClass()
      .getResourceAsStream("/models/en-lemmatizer.dict");
    DictionaryLemmatizer lemmatizer = new DictionaryLemmatizer(
      dictLemmatizer);
    String[]lemmas = lemmatizer.lemmatize(tokens, tags);

    assertThat(lemmas)
      .contains("O", "have", "a", "sister", "name", "O", "O");
}

ご覧のとおり、すべてのトークンについて補題を得ます。 「O」は、単語が固有名詞であるため、補題を決定できなかったことを示します。ですから、「John」と「Penny」の補題はありません。

しかし、文の他の単語に対する補題を特定しました。

  • 持っている

  • a – a

  • 姉妹 – 姉妹

  • 名前付き – 名前

8.チャンク

品詞情報もチャンキングに不可欠です –

@Test
public void
  givenChunkerModel__whenChunk__thenChunksAreDetected()
  throws Exception {

    SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
    String[]tokens = tokenizer.tokenize("He reckons the current account
      deficit will narrow to only 8 billion.");

    InputStream inputStreamPOSTagger = getClass()
      .getResourceAsStream("/models/en-pos-maxent.bin");
    POSModel posModel = new POSModel(inputStreamPOSTagger);
    POSTaggerME posTagger = new POSTaggerME(posModel);
    String tags[]= posTagger.tag(tokens);

    InputStream inputStreamChunker = getClass()
      .getResourceAsStream("/models/en-chunker.bin");
    ChunkerModel chunkerModel
     = new ChunkerModel(inputStreamChunker);
    ChunkerME chunker = new ChunkerME(chunkerModel);
    String[]chunks = chunker.chunk(tokens, tags);
    assertThat(chunks).contains(
      "B-NP", "B-VP", "B-NP", "I-NP",
      "I-NP", "I-NP", "B-VP", "I-VP",
      "B-PP", "B-NP", "I-NP", "I-NP", "O");
}

ご覧のとおり、各トークンの出力はチャンクから取得されます。 「B」はチャンクの開始を表し、「I」はチャンクの継続を表し、「O」はチャンクがないことを表します。

この例の出力を解析すると、6つのチャンクが得られます。

  1. “ He” – 名詞フレーズ

  2. “ reckons” – 動詞句

  3. “経常赤字” – 名詞フレーズ

  4. 「狭くなります」 – 動詞句

  5. “ to” – 前置詞句

  6. “たった80億” – 名詞句

9.言語検出

言語検出のためには、トレーニングデータファイルが必要です。このようなファイルには、特定の言語の文を含む行が含まれています。各行は、機械学習アルゴリズムへの入力を提供するために正しい言語でタグ付けされています。

言語検出のためのサンプルトレーニングデータファイルはhttps://github.com/apache/opennlp/blob/master/opennlp-tools/src/test/resources/opennlp/tools/doccat/DoccatSample.txtをダウンロードすることができます。

@Test
public void
  givenLanguageDictionary__whenLanguageDetect__thenLanguageIsDetected()
  throws FileNotFoundException, IOException {

    InputStreamFactory dataIn
     = new MarkableFileInputStreamFactory(
       new File("src/main/resources/models/DoccatSample.txt"));
    ObjectStream lineStream = new PlainTextByLineStream(dataIn, "UTF-8");
    LanguageDetectorSampleStream sampleStream
     = new LanguageDetectorSampleStream(lineStream);
    TrainingParameters params = new TrainingParameters();
    params.put(TrainingParameters.ITERATIONS__PARAM, 100);
    params.put(TrainingParameters.CUTOFF__PARAM, 5);
    params.put("DataIndexer", "TwoPass");
    params.put(TrainingParameters.ALGORITHM__PARAM, "NAIVEBAYES");

    LanguageDetectorModel model = LanguageDetectorME
      .train(sampleStream, params, new LanguageDetectorFactory());

    LanguageDetector ld = new LanguageDetectorME(model);
    Language[]languages = ld
      .predictLanguages("estava em uma marcenaria na Rua Bruno");
    assertThat(Arrays.asList(languages))
      .extracting("lang", "confidence")
      .contains(
        tuple("pob", 0.9999999950605625),
        tuple("ita", 4.939427661577956E-9),
        tuple("spa", 9.665954064665144E-15),
        tuple("fra", 8.250349924885834E-25)));
}

5.まとめ

ここでは、OpenNLPの興味深い機能からたくさんのことを調べました。語彙化、POSタグ付け、トークン化、文検出、言語検出などのNLPタスクを実行するためのいくつかの興味深い機能に焦点を当てました。

いつものように、上記すべての完全な実装はhttps://github.com/eugenp/tutorials/tree/master/apache-opennlp[over on GitHub]にあります。