1. 概要

Apache OpenNLPは、オープンソースの自然言語処理Javaライブラリです。

固有表現抽出、文検出、品詞タグ付け、トークン化などのユースケース向けのAPIを備えています。

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

2. Mavenのセットアップ

まず、pom.xmlに主な依存関係を追加する必要があります。

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

最新の安定バージョンは、 MavenCentralにあります。

一部のユースケースでは、トレーニング済みのモデルが必要です。 事前定義されたモデルここおよびこれらのモデルの詳細情報ここをダウンロードできます。

3. 文の検出

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

文の検出とは、文の開始と終了を識別することです。これは通常、手元の言語によって異なります。 これは「文の境界の明確化」(SBD)とも呼ばれます。 

場合によっては、文の検出は、ピリオド文字の性質があいまいであるため、非常に困難です。 ピリオドは通常、文の終わりを示しますが、電子メールアドレス、略語、小数、およびその他の多くの場所に表示されることもあります。

ほとんどのNLPタスクの場合、文の検出には、入力としてトレーニング済みのモデルが必要です。これは、 /resourcesフォルダーにあると予想されます。

文の検出を実装するには、モデルをロードして、次のインスタンスに渡します。 SentenceDetectorME。 次に、テキストをsentDetect()メソッドに渡して、文の境界で分割します。

@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を使用する

この場合、最初にモデルをロードする必要があります。 モデルファイルをここからダウンロードし、 / 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. WhitespaceTokenizer

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

@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つの異なるトークンではなく、単一のトークンとして。

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. 音声部分のタグ付け

入力としてトークンのリストが必要なもう1つのユースケースは、音声部分のタグ付けです。

品詞(POS)は、単語のタイプを識別します。 OpenNLPは、さまざまな品詞に次のタグを使用します。

  • NN – 名詞、単数または質量
  • DT –限定詞
  • VB – 動詞、基本形
  • VBD – 動詞、過去形
  • VBZ – 動詞、三人称単数存在
  • IN –前置詞または従属接続詞
  • NNP – 適切な名詞、単数
  • TO –「to」という単語
  • JJ –形容詞

これらは、ペンツリーバンクで定義されているものと同じタグです。 完全なリストについては、このリストを参照してください。

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. 「ジョン」– NNP(適切な名詞)
  2. 「持っている」– VBZ(動詞)
  3. 「a」– DT(限定詞)
  4. 「姉妹」– NN(名詞)
  5. 「名前付き」– VBZ(動詞)
  6. 「ペニー」– NNP(適切な名詞)
  7. 「。」 – 限目

7. Lemmatization

文中のトークンの品詞情報が得られたので、テキストをさらに分析できます。

補題は、時制、性別、気分、またはその他の情報を持つことができる単語形式を単語の基本形式(「補題」とも呼ばれる)にマッピングするプロセスです。

補題は、トークンとその品詞タグを入力として受け取り、単語の補題を返します。 したがって、字句解析の前に、文は品詞タグ付けと品詞タグ付けを通過する必要があります。

Apache OpenNLPは、次の2種類のレンマ化を提供します。

  • 統計– には、特定の単語の見出語を見つけるためのトレーニングデータを使用して構築された見出語モデルが必要です
  • 辞書ベース– には、単語、POSタグ、および対応する補題のすべての有効な組み合わせを含む辞書が必要です。

統計的レンマ化の場合はモデルをトレーニングする必要がありますが、辞書のレンマ化の場合はこのような辞書ファイルが必要です。

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

@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」は、単語が適切な名詞であるため、見出語を判別できなかったことを示します。 したがって、「ジョン」と「ペニー」の補題はありません。

しかし、文の他の単語の見出語を特定しました。

  • 持っている
  • a – a
  • 姉妹–姉妹
  • 名前付き–名前

8. チャンキング

品詞情報もチャンク化に不可欠です– 文を名詞グループや動詞グループのような文法的に意味のある単語グループに分割します。

前と同様に、 branch()メソッドを呼び出す前に、文をトークン化し、トークンに品詞タグ付けを使用します。

@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. 「彼」–名詞句
  2. 「reckons」–動詞句
  3. 「経常収支赤字」–名詞句
  4. 「狭くなります」–動詞句
  5. 「to」–前置詞句
  6. 「わずか80億」–名詞句

9. 言語検出

すでに説明したユースケースに加えて、 OpenNLPは、特定のテキストの言語を識別できる言語検出APIも提供します。 

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

言語検出用のサンプルトレーニングデータファイルは、こちらからダウンロードできます。

トレーニングデータファイルをLanguageDetectorSampleStreamにロードし、いくつかのトレーニングデータパラメータを定義し、モデルを作成してから、モデルを使用してテキストの言語を検出できます。

@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の興味深い機能から多くのことを探求しました。 字句解析、品詞タグ付け、トークン化、文検出、言語検出などのNLPタスクを実行するためのいくつかの興味深い機能に焦点を当てました。

いつものように、上記のすべての完全な実装は、GitHubにあります。