OpenNLP介绍与实践指南

2025/11/18 自然语言

# 1. OpenNLP是什么

OpenNLP是一个开源的自然语言处理(NLP)工具包,由Apache软件基金会开发和维护。它提供了一系列自然语言处理功能,能够帮助开发者轻松实现文本分析、理解和生成等任务。

OpenNLP的设计目标是提供一个灵活、高效且易于使用的NLP工具集,使开发者能够快速构建自然语言处理应用,而无需深入了解底层复杂的算法细节。

OpenNLP项目始于2000年代初期,最初由惠普实验室开发。2006年,该项目被捐赠给Apache软件基金会,并成为Apache孵化器项目。经过多年发展,OpenNLP于2010年成为Apache顶级项目,目前已成为最受欢迎的开源NLP工具包之一。

# 2. OpenNLP 核心组件

它提供了一系列模块化、可训练的组件,让你可以构建针对特定领域的高精度文本处理流水线,以下是对其六大基础能力的详细拆解,完全从 “功能 -> 应用 -> 需求联想” 的视角出发。

# 2.1. 分词(文本剪刀)

  • 工具比喻:将一段连续的文本切分成一个个独立的词语或符号(即 “词元” 或 “词元” ),这是所有后续处理的基础。对于中文这种没有天然空格分隔的语言来说,分词尤为重要和复杂。
  • 在OpenNLP中的组件: Tokenizer 或 TokenizerME 组件。它学习文本中的空格、标点等特征,来确定词的边界。
  • 实际应用场景
    • 搜索引擎: 当你输入“北京的天气”时,搜索引擎需要将其切分为【北京】【的】【天气】才能进行检索。
    • 所有文本处理的第一步: 无论是情感分析、翻译还是智能问答,第一步几乎都是分词。
  • 需求联想: 只要你的任务需要以“词”为单位进行处理,第一步就是分词。“我这个需求,需要先把句子拆开成一个个词吗?”

# 2.2. 词性标注(语法标签贴)

  • 工具比喻:在分词的基础上,为每一个词打上一个“语法标签”,标明它是名词、动词、形容词、副词等。
  • 在OpenNLP中的组件: POSTagger 或 POSTaggerME 组件。它根据词语本身及其上下文来判断词性。
  • 实际应用场景
    • 信息提取: 快速找到句子中的主要实体(通常是名词)和关键动作(通常是动词)。例如,在“苹果发布了新款iPhone”中,可以识别出“苹果”和“iPhone”是名词/实体,“发布”是动词/动作。
    • 语法检查与拼写校正: 帮助判断一个词在上下文中的用法是否正确(例如,“我读了一本书” vs. “我书了一本读”)。
    • 快速找到关键信息: 你想从一段评论“这台手机拍照非常清晰,电池也很耐用”中,快速提取用户关心的“东西”(名词)和“评价”(形容词)。贴上标签后,你就可以重点关注“手机”、“拍照”、“电池”(名词)和“清晰”、“耐用”(形容词)。
    • 智能校对与改写: 检查一句话的语法是否通顺,比如动词前面是不是少了主语。
  • 需求联想: “我需要区分句子里的‘东西’和‘动作’吗?” 或者 “我需要知道用户是在描述一个‘属性’还是在执行一个‘动作’吗?”

# 2.3. 命名实体识别(高亮标记笔)

  • 工具比喻: 识别文本中具有特定意义的实体,并将其分类到预定义的类别中,如人名地名组织机构名时间金额等。 高亮标记笔。用来在文本中标记并分类出所有专有名词。
  • 在OpenNLP中的组件: NameFinder 或 NameFinderME 组件。这是NLP中非常实用和关键的一步。
  • 实际应用场景
    • 舆情监控: 自动从新闻和社交媒体中识别出被讨论的公司、人物和产品。
    • 知识图谱构建: 从非结构化文本中提取实体,作为知识图谱的“节点”。
    • 客户服务自动化: 从用户提问“帮我查一下北京到上海的机票”中,自动提取出发地和目的地。
    • 自动提取简历信息: 从海量简历中自动提取出“候选人姓名”、“毕业院校”、“工作公司”、“工作年限”、“技能名称”。
    • 新闻自动打标: 一篇新闻报道出来,自动识别出涉及的“人物”、“地点”、“组织机构”、“时间”,并生成标签,便于分类和检索。
    • 客服工单自动分类: 用户说“我的iPhone 15无法开机了”,识别出“iPhone 15”是一个产品实体,从而自动将工单分配给“硬件支持”团队。
  • 需求联想: “我需要从文字里自动找出并归类真人名、公司名、地名、产品名、时间、金额吗?”。 这是NLP中最常用、最立竿见影的能力之一。

# 2.4. 分句(段落切刀)

  • 工具比喻: 将一整段文字(比如一个段落)分割成独立的句子。
  • 在OpenNLP中的组件: SentenceDetector 或 SentenceDetectorME 组件。它主要根据句号、问号、感叹号等标点符号进行判断,但需要处理像“Dr. Smith”这种包含点的缩写。
  • 实际应用场景
    • 机器翻译: 先将长文本拆分成句子,再对每个句子进行翻译,效果更好。
    • 文本分析: 很多分析任务(如情感分析)以句子为单位进行会更精确。
    • 处理长文档: 在对一篇长报告进行分析或翻译时,先把它切成独立的句子,再逐句处理,准确率会高很多。
    • 让分析更精确: 做情感分析时,一段话可能同时包含正面和负面评价,比如“手机很好,但服务太差了”。切成两个句子后,就能分别判断出正面和负面情绪。
  • 需求联想: “我处理的文本很长,需要拆成短句来逐一分析吗?”

# 2.5. 句法分析(句子结构解剖图)

  • 工具比喻:分析句子的语法结构,确定词语之间的依赖关系,比如哪个是主语、哪个是谓语、哪个是宾语。 句子结构解剖图。分析词与词之间的“主谓宾”等语法关系。
  • 在OpenNLP中的组件: Parser 组件。其输出通常是一棵“语法分析树”。
  • 实际应用场景
    • 关系提取: 要理解“马云创立了阿里巴巴”中“马云”和“阿里巴巴”是“创立”关系,而不仅仅是两个独立的实体,就需要句法分析。
    • 高级搜索: 支持更精确的搜索,比如搜索“Java教的好的老师”,系统可以理解“教”是核心动作,“老师”是主语,从而返回更相关的结果。
    • 问答系统: 帮助系统理解问句的结构,例如“谁创立了苹果公司?”中,“谁”是询问的主语。
    • 理解用户命令: 处理复杂指令,如“帮我关闭卧室的灯”。你需要理解“你”是主语,“关闭”是动作,“灯”是宾语,“卧室的”是修饰灯的。这样才能准确执行。
    • 精准搜索: 搜索“苹果的创始人”,句法分析能帮你理解你想找的是“创始人”(主体)是“苹果”(所属),而不是关于“苹果”这个水果的新闻。
    • 发现实体间关系: 在“马云创立了阿里巴巴”中,命名实体识别能找到“马云”和“阿里巴巴”,但句法分析能告诉你他们之间是“创立”关系。
  • 需求联想: “我需要理解一句话的精确含义,而不仅仅是里面的词吗?” 或 “我需要知道句子中不同实体之间是什么关系吗?”

# 2.6. 指代消解(侦探推理术)

  • 工具比喻: 确定文本中代词(如“他”、“她”、“它”、“这”、“那”)或名词短语所指代的具体对象。这是NLP中最具挑战性的任务之一。
  • 在OpenNLP中的组件:Coreference 组件。它需要联系上下文进行推理。
  • 实际应用场景
    • 文档摘要: 要生成连贯的摘要,必须理解“该公司”、“他”等指代的是什么。例如:“苹果发布了新手机。它采用了最新的芯片。” 指代消解需要知道“它”指的是“新手机”。
    • 深度阅读理解: 对于法律文书、小说等长文本,理清人物和事物的指代关系至关重要。
    • 自动生成摘要: 为一篇小说生成摘要,你必须搞清楚所有“他”、“她”具体指代哪个角色,否则摘要会不知所云。
    • 深度阅读理解: 分析一篇法律合同或技术报告,需要理清所有“该条款”、“上述产品”、“其责任方”所指代的具体内容,避免歧义。
  • 需求联想: “我处理的文本中代词很多,需要搞清楚这些代词到底指代什么,才能正确理解文意吗?”

# 3. 基于基础能力的综合应用

# 3.1. 语义相似度计算 - 语义比对仪

  • 是什么:量化两段文本在含义上的相似程度,而非字面匹配。这是一种 “表示学习”
  • 类比:就像教电脑学会查字典和理解词义,但让它自己根据含义的远近,把单词放到一个多维地图上。
  • 实际应用场景
    • 智能客服:判断用户问题“我无法登录了”和“登录失败”是相似问题,返回同一答案。
    • 论文查重:检测语义上的抄袭,而不仅仅是文字复制。
    • 搜索增强:提升搜索引擎的召回率,使其能理解同义词和相近表达。
  • 依赖的基础能力:所有基础能力(用于深度理解),但更关键的是通过词向量句子嵌入等技术将文本转化为数值向量。
  • 需求联想:我需要判断两段文字表达的意思是否相近吗?

# 3.2. 文本分类 - 自动归档员

  • 是什么:为整段文本分配一个或多个预定义的类别标签。这是一种典型的 “判别式学习”

  • 类比:就像教一个孩子做判断题,给他看大量带答案(标签)的例题(数据),让他学会自己判断对错。

  • OpenNLP组件: DocumentCategorizer

  • 实际应用场景

    • ①情感分析:判断评论是“正面”还是“负面”。

      • 舆情监控: 自动判断社交媒体上关于你公司产品的讨论是“正面”、“负面”还是“中性”。
      • 电商评价分析: 自动将海量用户评价分为“好评”、“中评”、“差评”,快速了解用户满意度。

      需求联想: “我需要判断一段文字的整体情感倾向或立场吗?”

    • ②主题归类:将新闻自动分到“体育”、“财经”等板块。

      • 新闻网站: 自动将新闻文章归类到“体育”、“财经”、“科技”、“娱乐”等频道。
      • 知识库管理: 自动将用户提交的工单或问题分到“账户问题”、“技术故障”、“支付疑问”等不同的支持类别,以便路由给正确的处理团队。

      需求联想: “我需要根据内容,把文本自动放到不同的主题篮子里吗?”

    • ③意图识别:在客服机器人中理解用户是想“查询物流”还是“申请退货”。

      • 智能客服/聊天机器人: 理解用户一句话背后的真实目的。例如:
        • “我的订单什么时候到?” -> 意图: 查询物流
        • “我要退货。” -> 意图: 申请退货
        • “帮我订一张明天去北京的机票。” -> 意图: 订购机票

      需求联想: “我需要理解用户‘想干什么’,而不是仅仅分析他‘说了什么’吗?”

    • ④垃圾信息过滤:自动识别垃圾邮件或违规内容。

      • 邮箱服务: 自动识别并过滤“垃圾邮件”。
      • 社区平台: 自动识别“广告贴”、“违规内容”、“引战言论”等。

      需求联想: “我需要自动判断一条信息是否是‘垃圾’或‘不受欢迎’的吗?”

  • 依赖的基础能力:分词、词性标注、NER(用于提取特征)。

  • 需求联想:我需要根据内容,把文本自动放到不同的主题或情感篮子里吗?

# 3.3. 两者对比

这两种技术都与大数据有关联,但关系不同:

  • Embedding模型训练:需要海量无标注文本大数据(如互联网上的全部网页、书籍、新闻等)进行预训练,才能学会语言的通用语义。这个过程计算代价巨大,通常由大型机构完成。但您使用它时,不需要自己的大数据,可以直接下载预训练好的模型。
  • 文本分类模型训练:除了需要预训练,其微调阶段严重依赖您自己业务场景的标注数据。您需要积累一个高质量的、“小而精”的标注数据集。这个数据的质量直接决定模型的最终效果。
特性 基于Embedding的语义相似度 训练文本分类模型
技术范畴 NLP - 表示学习 / 无监督学习 NLP - 判别式学习 / 监督学习
核心任务 将文本映射到向量空间,度量相似度 学习从文本到分类标签的映射函数
数据依赖 依赖通用的预训练模型,对自有标注数据需求低 强烈依赖自有业务的标注数据
输出结果 一个相似度分数(如0.92) 一个分类标签及置信度(如:告警,96%)
灵活性 。可以灵活应对新的表述,因为关键是语义是否相似。 相对固定。只能识别训练时见过的类别,对全新模式不敏感。
开发成本 相对较低。主要是集成和调优预训练模型。 相对较高。需要数据标注、模型训练/微调全流程。
可解释性 较低。很难解释为什么两个向量是相似的。 中等。可以通过注意力机制等看到模型关注了哪些词。

# 3.4. 其他重要的综合能力

  • 文本聚类:与分类不同,它无监督地自动发现文本中的隐藏主题。(应用:从用户反馈中自动发现主流话题)。
  • 自动摘要:将长文档压缩为短摘要。
    • 抽取式摘要:综合运用NER、句法分析等抽取关键句。
    • 生成式摘要:在理解基础上重新生成文本(这更多是现代LLM的强项)。
  • 问答系统:从给定文本中精准回答用户问题。(深度融合了NER、句法分析、语义相似度和指代消解)。
  • OpenNLP支持多种语言,包括但不限于:英语、中文、德语、法语、西班牙语、葡萄牙语、荷兰语、意大利语

# 4. OpenNLP 在 AI 领域的定位与未来

# 4.1. OpenNLP 的独特价值

  1. 轻量可控:模型小巧,部署成本低,适合嵌入式或边缘设备。
  2. 领域定制:你可以用自己的数据训练专属模型,在特定领域(如医疗、金融)达到很高精度。
  3. 数据隐私:所有处理可在本地完成,无需上传数据到云端,满足严格的隐私要求。
  4. 透明与学习:其基于统计模型的机制相对透明,非常适合作为学习NLP原理和流程的入门工具。
特性 Apache OpenNLP 现代大语言模型
技术路径 基于规则的统计模型 基于深度学习的 Transformer 架构
核心优势 轻量、可定制、隐私友好、成本低 强大的语言理解、生成、逻辑推理能力
典型任务 精准的结构化解析、特定领域分类 对话、创作、复杂问答、跨领域任务
资源需求 非常高
关系 专业工具 通才

结论:它们并非替代关系,而是互补。一个常见的协作模式是:使用 OpenNLP 进行高效的、定制化的数据预处理(如从专业文档中提取实体),然后将清洗后的结构化信息交给 LLM 进行深度的理解和生成

# 4.2. 与其他NLP工具对比

特性 OpenNLP Stanford NLP DL4J
定位 轻量级、易集成 功能全面、高准确率 深度学习框架
协议 Apache 2.0 GPL Apache 2.0
速度 较快 略慢 一般
准确率 中等 可定制高
学习曲线
适用场景 资源有限、快速集成 高准确率要求(金融/医疗) 需要深度学习模型

关键区别

  • OpenNLP适合快速集成到现有Java应用
  • Stanford NLP功能更全,但配置复杂
  • DL4J更适合构建深度学习模型,不是传统NLP工具

# 4.3. OpenNLP的应用场景

# 4.3.1 企业应用

OpenNLP在企业环境中有广泛应用:

  • 智能客服系统:自动理解用户查询并提供相关回答
  • 内容管理系统:自动分类和标记文档,提高内容管理效率
  • 商业智能:从客户反馈、社交媒体和其他文本源中提取有价值的商业信息
  • 信息安全:文本内容过滤、垃圾邮件检测和安全审计
  • 搜索引擎:改进搜索相关性和结果排序

# 4.3.2 研究领域

在学术研究中,OpenNLP常被用于:

  • 计算语言学研究
  • 自然语言处理算法开发与验证
  • 多语言处理研究
  • 文本挖掘和信息检索研究

# 4.3.3 开源项目

OpenNLP被集成到多个开源项目中:

  • Apache UIMA:非结构化信息管理架构
  • Apache Tika:内容检测和分析工具
  • Elasticsearch:搜索引擎的文本分析插件
  • Apache Lucene:全文搜索引擎库

# 5. 实战:OpenNLP进行文本分类

# 5.1 任务描述

我们将使用OpenNLP构建一个文本分类器,实现对新闻文章的分类。具体任务是将新闻文本分为"体育"、"政治"、"经济"和"科技"四个类别。

# 5.2 实现流程

  1. 准备训练数据:收集已分类的新闻文本作为训练样本
  2. 训练文本分类模型:使用OpenNLP的文档分类器训练模型
  3. 加载模型并进行分类预测:使用训练好的模型对新文本进行分类
  4. 评估分类效果:分析分类准确性并优化模型

# 5.3 代码实现

maven引入

<!-- openNLP -->
<dependency>
    <groupId>org.apache.opennlp</groupId>
    <artifactId>opennlp-tools</artifactId>
    <version>1.9.4</version>
</dependency>
1
2
3
4
5
6

代码示例1

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import opennlp.tools.doccat.DoccatFactory;
import opennlp.tools.doccat.DoccatModel;
import opennlp.tools.doccat.DocumentCategorizerME;
import opennlp.tools.doccat.DocumentSample;
import opennlp.tools.doccat.DocumentSampleStream;
import opennlp.tools.util.InputStreamFactory;
import opennlp.tools.util.MarkableFileInputStreamFactory;
import opennlp.tools.util.ObjectStream;
import opennlp.tools.util.PlainTextByLineStream;
import opennlp.tools.util.TrainingParameters;

public class NewsClassifier {
    
    private DoccatModel model;
        
    // 训练分类模型 trainingDataPath-训练数据
    public void trainModel(String trainingDataPath) throws IOException {
        InputStreamFactory inputStreamFactory = new MarkableFileInputStreamFactory(new File(trainingDataPath));
        ObjectStream<String> lineStream = new PlainTextByLineStream(inputStreamFactory, StandardCharsets.UTF_8);
        ObjectStream<DocumentSample> sampleStream = new DocumentSampleStream(lineStream);
        
        TrainingParameters params = new TrainingParameters();
        params.put(TrainingParameters.ITERATIONS_PARAM, 100);
        params.put(TrainingParameters.CUTOFF_PARAM, 5);
        
        model = DocumentCategorizerME.train("en", sampleStream, params, new DoccatFactory());
    }
    
    // 对文本进行分类
    public String classifyText(String text) {
        DocumentCategorizerME categorizer = new DocumentCategorizerME(model);
        double[] probabilities = categorizer.categorize(text);
        String category = categorizer.getBestCategory(probabilities);
        
        // 打印分类概率
        System.out.println("分类概率:");
        for (int i = 0; i < probabilities.length; i++) {
            System.out.println(categorizer.getCategory(i) + ": " + probabilities[i]);
        }
        
        return category;
    }
    
    public static void main(String[] args) {
        try {
            NewsClassifier classifier = new NewsClassifier();
            
            // 训练模型
            System.out.println("开始训练模型...");
            classifier.trainModel("news_train.txt");
            System.out.println("模型训练完成!");
            
            // 测试分类
            String testText1 = "The president announced new economic policies today, focusing on tax reforms and job creation.";
            System.out.println("
测试文本 1: " + testText1);
            String category1 = classifier.classifyText(testText1);
            System.out.println("分类结果: " + category1);
            
            String testText2 = "The team won the championship after a thrilling final match that went into extra time.";
            System.out.println("
测试文本 2: " + testText2);
            String category2 = classifier.classifyText(testText2);
            System.out.println("分类结果: " + category2);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

代码示例2

// 训练阶段(一次性执行,生成模型文件——> .bin文件)
public class ModelTrainer {
    public static void main(String[] args) throws IOException {
        // 准备训练数据
        ObjectStream<String> lineStream = new PlainTextByLineStream(
            () -> new FileInputStream("training_data.txt"), "UTF-8");
        ObjectStream<DocumentSample> sampleStream = new DocumentSampleStream(lineStream);
        
        // 训练参数
        TrainingParameters params = new TrainingParameters();
        params.put(TrainingParameters.ITERATIONS_PARAM, 100);
        params.put(TrainingParameters.CUTOFF_PARAM, 5);
        
        // 训练模型
        DoccatModel model = DocumentCategorizerME.train("zh", sampleStream, params, new DoccatFactory());
        
        // 保存模型到文件(只需执行一次)
        try (OutputStream modelOut = new BufferedOutputStream(new FileOutputStream("alert_model.bin"))) {
            model.serialize(modelOut);
        }
        
        System.out.println("模型训练完成,已保存为 alert_model.bin");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 运行阶段(项目启动时加载模型,不需要重新训练)

import opennlp.tools.doccat.*;
import opennlp.tools.tokenize.SimpleTokenizer;
import java.io.*;

public class AlertClassifier {

    private DoccatModel model;

    // 加载之前训练好的模型
    public void loadModel(String modelPath) throws IOException {
        try (InputStream modelIn = new FileInputStream(modelPath)) {
            model = new DoccatModel(modelIn);
        }
    }

    // 对新的消息进行分类
    public ClassificationResult classify(String message) throws IOException {
        // 获取分类器
        DocumentCategorizerME categorizer = new DocumentCategorizerME(model);
        
        // 对消息进行分词(也可以尝试更复杂的分词器)
        String[] tokens = SimpleTokenizer.INSTANCE.tokenize(message);
        
        // 分类,并得到所有类别的概率
        double[] outcomes = categorizer.categorize(tokens);
        String category = categorizer.getBestCategory(outcomes);
        // 最佳类别的置信度
        double confidence = outcomes[categorizer.getIndex(category)];

        return new ClassificationResult(category, confidence);
    }

    // 定义一个类来封装结果
    public static class ClassificationResult {
        public final String category;
        public final double confidence;
        public ClassificationResult(String category, double confidence) {
            this.category = category;
            this.confidence = confidence;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 5.4 效果展示与分析

假设我们使用包含4个类别的新闻数据集进行训练,训练完成后对测试文本进行分类:

测试文本 1: "The president announced new economic policies today, focusing on tax reforms and job creation." 分类结果: 政治 (概率: 0.85)

测试文本 2: "The team won the championship after a thrilling final match that went into extra time." 分类结果: 体育 (概率: 0.92)

分析:

  • OpenNLP文本分类器能够准确识别新闻文本的类别
  • 通过调整训练迭代次数和cutoff参数,可以进一步提高分类准确性
  • 对于特定领域的文本分类,可以通过增加该领域的训练数据来优化模型
  • 实际应用中,建议使用交叉验证等方法评估模型性能,并进行必要的调优

# 6. 如何为你的需求选择技术

当您面临一个开发需求时,可以遵循以下路径思考

  1. 需求拆解:我的需求是需要理解文本的结构,还是需要对文本做整体判断,或是需要创造内容
  2. 优势领域:它的主要优势在于轻量、可定制且对数据隐私友好。你可以用自己的数据训练专属模型,尤其适合处理金融、医疗等专业领域的文本。此外,作为开源工具,它在成本上也更具灵活性。
  3. 技术选型
    • 如果需要分词、找实体、分析语法 ——> 首选 OpenNLP 等基础工具包
    • 如果需要文本分类、情感分析、领域问答 ——> OpenNLP 可以胜任,但大语言模型效果通常更好。
    • 如果需要开放式对话、内容创作、复杂推理 ——> 首选 大语言模型(LLMs)
  4. 考虑约束:如果对数据隐私、计算成本、领域专业性要求极高,OpenNLP 往往是更优解
  5. 面临挑战:其挑战主要在于,面对ChatGPT、Claude、Gemini、DeepSeek等大语言模型(LLMs)在语言理解、生成和逻辑推理方面的强大能力,OpenNLP 在执行复杂的语义理解任务时,效果通常不如这些大模型。

虽然当前 AI 领域的焦点在大语言模型上,但 OpenNLP 这类传统工具包在特定场景下依然有不可替代的优势。简单来说,OpenNLP 更像是一套精准的专业工具,而大语言模型则是一个通才。它们并非简单的替代关系,而是可以协作——例如,使用 OpenNLP 进行高效的数据预处理,为大模型提供更干净的结构化输入。

# 7. 参考资料

  1. Apache OpenNLP官方网站: https://opennlp.apache.org/
  2. OpenNLP文档: https://opennlp.apache.org/docs/
  3. OpenNLP GitHub仓库: https://github.com/apache/opennlp
  4. OpenNLP模型库: https://opennlp.apache.org/models.html