使用 Apache OpenNLP 进行自然语言处理

自然语言处理 (NLP) 是软件领域最重要的前沿领域之一。 自数字计算诞生以来,基本思想——如何有效地使用和生成人类语言——一直是一项持续的努力。 今天,这种努力仍在继续,机器学习和图形数据库处于掌握自然语言的前沿。

本文是对 Apache OpenNLP 的实践介绍,这是一个基于 Java 的机器学习项目,它提供分块和词形还原等原语,这两者都是构建支持 NLP 的系统所必需的。

什么是 Apache OpenNLP?

Apache OpenNLP 等机器学习自然语言处理系统通常包含三个部分:

    Learning from a corpus, which is a set of textual data (plural: corpor) 从语料库生成的模型 使用模型对目标文本执行任务

为了让事情变得更简单,OpenNLP 为许多常见用例提供了预训练模型。 对于更复杂的要求,您可能需要训练自己的模型。 对于更简单的场景,您可以只下载现有模型并将其应用于手头的任务。

使用 OpenNLP 进行语言检测

让我们构建一个基本应用程序,我们可以使用它来了解 OpenNLP 的工作原理。 我们可以使用 Maven 原型开始布局,如清单 1 所示。

清单 1. 创建一个新项目


~/apache-maven-3.8.6/bin/mvn archetype:generate -DgroupId=com.infoworld.com -DartifactId=opennlp -DarchetypeArtifactId=maven-arhectype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false

这个原型将构建一个新的 Java 项目。 接下来,将 Apache OpenNLP 依赖项添加到 pom.xml 在项目的根目录中,如清单 2 所示。(您可以使用最新版本的 OpenNLP 依赖项。)

清单 2. OpenNLP Maven 依赖项


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

为了更容易执行程序,还要将以下条目添加到 <plugins> 的片段 pom.xml 文件:

清单 3. Maven POM 的主类执行目标


<plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>3.0.0</version>
            <configuration>
                <mainClass>com.infoworld.App</mainClass>
            </configuration>
        </plugin>

现在,运行程序 maven compile exec:java. (你需要安装 Maven 和 JDK 才能运行这个命令。)现在运行它只会给你熟悉的“Hello World!” 输出。

下载并设置语言检测模型

现在我们已准备好使用 OpenNLP 来检测示例程序中的语言。 第一步是下载语言检测模型。 从 OpenNLP 模型下载页面下载最新的语言检测器组件。 在撰写本文时,当前版本是 langdetect-183.bin。

为了使模型易于获取,让我们进入 Maven 项目并 mkdir 新目录位于 /opennlp/src/main/resource,然后复制 langdetect-*.bin 文件在那里。

现在,让我们将现有文件修改为您在清单 4 中看到的内容。我们将使用 /opennlp/src/main/java/com/infoworld/App.java 对于这个例子。

清单 4.App.java


package com.infoworld;

import java.util.Arrays;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import opennlp.tools.langdetect.LanguageDetectorModel;
import opennlp.tools.langdetect.LanguageDetector;
import opennlp.tools.langdetect.LanguageDetectorME;
import opennlp.tools.langdetect.Language;

public class App {
  public static void main( String[] args ) {
    System.out.println( "Hello World!" );
    App app = new App();
    try {
      app.nlp();
    } catch (IOException ioe){
      System.err.println("Problem: " + ioe);
    }
  }
  public void nlp() throws IOException {
    InputStream is = this.getClass().getClassLoader().getResourceAsStream("langdetect-183.bin"); // 1
    LanguageDetectorModel langModel = new LanguageDetectorModel(is); // 2
    String input = "This is a test.  This is only a test.  Do not pass go.  Do not collect $200.  When in the course of human history."; // 3
    LanguageDetector langDetect = new LanguageDetectorME(langModel); // 4
    Language langGuess = langDetect.predictLanguage(input); // 5

    System.out.println("Language best guess: " + langGuess.getLang());

    Language[] languages = langDetect.predictLanguages(input);
    System.out.println("Languages: " + Arrays.toString(languages));
  }
}

现在,您可以使用以下命令运行该程序, maven compile exec:java. 当您这样做时,您将获得类似于清单 5 中所示的输出。

清单 5. 语言检测运行 1



语言最佳猜测:eng 语言: [eng (0.09568318011427969), tgl (0.027236092538322446), cym (0.02607472496029117), war (0.023722424236917564)...

The “ME” in this sample stands for maximum entropy. Maximum entropy is a concept from statistics that is used in natural language processing to optimize for best results.

Evaluate the results

Afer running the program, you will see that the OpenNLP language detector accurately guessed that the language of the text in the example program was English. We’ve also output some of the probabilities the language detection algorithm came up with. After English, it guessed the language might be Tagalog, Welsh, or War-Jaintia. In the detector’s defense, the language sample was small. Correctly identifying the language from just a handful of sentences, with no other context, is pretty impressive.

Before we move on, look back at Listing 4. The flow is pretty simple. Each commented line works like so:

    Open the langdetect-183.bin file as an input stream.
    Use the input stream to parameterize instantiation of the LanguageDetectorModel.
    Create a string to use as input.
    Make a language detector object, using the LanguageDetectorModel from line 2.
    Run the langDetect.predictLanguage() method on the input from line 3.

Testing probability

If we add more English language text to the string and run it again, the probability assigned to eng should go up. Let’s try it by pasting in the contents of the United States Declaration of Independence into a new file in our project directory: /src/main/resources/declaration.txt. We’ll load that and process it as shown in Listing 6, replacing the inline string:

Listing 6. Load the Declaration of Independence text


String input = new String(this.getClass().getClassLoader().getResourceAsStream("declaration.txt").readAllBytes());

If you run this, you’ll see that English is still the detected language.

Detecting sentences with OpenNLP

You’ve seen the language detection model at work. Now, let’s try out a model for detecting sentences. To start, return to the OpenNLP model download page, and add the latest Sentence English model component to your project’s /resource directory. Notice that knowing the language of the text is a prerequisite for detecting sentences.

We’ll follow a similar pattern to what we did with the language detection model: load the file (in my case opennlp-en-ud-ewt-sentence-1.0-1.9.3.bin) and use it to instantiate a sentence detector. Then, we’ll use the detector on the input file. You can see the new code in Listing 7 (along with its imports); the rest of the code remains the same.

Listing 7. Detecting sentences


import opennlp.tools.sentdetect.SentenceModel;
import opennlp.tools.sentdetect.SentenceDetectorME;
//...
InputStream modelFile = this.getClass().getClassLoader().getResourceAsStream("opennlp-en-ud-ewt-sentence-1.0-1.9.3.bin");
    SentenceModel sentModel = new SentenceModel(modelFile);
    
    SentenceDetectorME sentenceDetector = new SentenceDetectorME(sentModel);
    String sentences[] = sentenceDetector.sentDetect(输入);  System.out.println("句子:" + sentences.length + "第一行:"+ sentences[2])

现在运行该文件将输出如清单 8 所示的内容。

清单 8. 句子检测器的输出


Sentences: 41 first line: In Congress, July 4, 1776

The unanimous Declaration of the thirteen united States of America, When in the Course of human events, ...

请注意,句子检测器找到了 41 个句子,这听起来是对的。 另请注意,此检测器模型非常简单:它仅查找句点和空格以找到中断点。 它没有语法逻辑。 这就是为什么我们在 sentences 数组上使用索引 2 来获取实际的序言——标题行被合并为两个句子。 (创始文件与标点符号不一致是出了名的,句子检测器也没有尝试将“When in the Course ……”视为一个新句子。)

使用 OpenNLP 标记化

在将文档分解成句子之后,标记化是下一个粒度级别。 标记化是将文档分别分解为单词和标点符号的过程。 我们可以使用清单 9 中所示的代码:

清单 9. 标记化


import opennlp.tools.tokenize.SimpleTokenizer;
//...
SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE;
    String[] tokens = tokenizer.tokenize(input);
    System.out.println("tokens: " + tokens.length + " : " + tokens[73] + " " + tokens[74] + " " + tokens[75]);

这将产生如清单 10 所示的输出。

清单 10. 分词器输出


tokens: 1704 : human events ,

因此,该模型将文档分解为 1704 个标记。 我们可以访问令牌数组,单词“human events”和后面的逗号,每个都占据一个元素。

使用 OpenNLP 查找名称

现在,我们将获取英文的“人名查找器”模型,称为 en-ner-person.bin。 并不是说这个模型位于 Sourceforge 模型下载页面上。 获得模型后,将其放在项目的资源目录中,并使用它在文档中查找名称,如清单 11 所示。

清单 11. 使用 OpenNLP 进行名称查找


import opennlp.tools.namefind.TokenNameFinderModel;
import opennlp.tools.namefind.NameFinderME;
import opennlp.tools.namefind.TokenNameFinder;
import opennlp.tools.util.Span
//...
InputStream nameFinderFile = this.getClass().getClassLoader().getResourceAsStream("en-ner-person.bin");
    TokenNameFinderModel nameFinderModel = new TokenNameFinderModel(nameFinderFile);
    NameFinderME nameFinder = new NameFinderME(nameFinderModel);
    Span[] names = nameFinder.find(tokens);
    System.out.println("names: " + names.length);
    for (Span nameSpan : names){
      System.out.println("name: " + nameSpan + " : " + tokens[nameSpan.getStart()-1] + " " + tokens[nameSpan.getEnd()-1]);
}

在清单 11 中,我们加载模型并使用它来实例化一个 NameFinderME 对象,然后我们使用它来获取名称数组,建模为 span 对象。 跨度有一个开始和结束,告诉我们检测器认为名称在标记集中的开始和结束位置。 请注意,名称查找器需要一个已经标记化的字符串数组。

训练模型

与其他模型不同,名称查找模型做得并不好。 这是一个训练自定义模型或使用从不同数据集构建的模型可能有意义的实例。 训练名称模型超出了本文的范围,但您可以在 OpenNLP 页面上了解更多信息。

使用 OpenNLP 标记词性

OpenNLP 允许我们根据标记化字符串标记词性 (POS)。 清单 12 是词性标注的示例。

清单 12. 词性标注


import opennlp.tools.postag.POSModel;
import opennlp.tools.postag.POSTaggerME;
//…
InputStream posIS = this.getClass().getClassLoader().getResourceAsStream("opennlp-en-ud-ewt-pos-1.0-1.9.3.bin");
POSModel posModel = new POSModel(posIS);
POSTaggerME posTagger = new POSTaggerME(posModel);
String tags[] = posTagger.tag(tokens);
System.out.println("tags: " + tags.length);

for (int i = 0; i < 15; i++){
  System.out.println(tokens[i] + " = " + tags[i]);
}

该过程类似于将模型文件加载到模型类中,然后在标记数组上使用。 它输出类似于清单 13 的内容。

清单 13. 词性输出


tags: 1704
Declaration = NOUN
of = ADP
Independence = NOUN
: = PUNCT
A = DET
Transcription = NOUN
Print = VERB
This = DET
Page = NOUN
Note = NOUN
: = PUNCT
The = DET
following = VERB
text = NOUN
is = AUX

与名称查找模型不同,词性标注器做得很好。 它正确地识别了几个不同的词性。 清单 13 中的示例包括 NOUN、ADP(代表副词)和 PUNCT(代表标点符号)。

结论

在本文中,您了解了如何将 Apache OpenNLP 添加到 Java 项目并使用预构建的模型进行自然语言处理。 在某些情况下,您可能需要开发自己的模型,但预先存在的模型通常可以解决问题。 除了此处演示的模型之外,OpenNLP 还包括文档分类器、词形还原器(将单词分解为词根)、词块划分器和解析器等功能。 全部…

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注