OpenNLP介绍与实践指南
# 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 的独特价值
- 轻量可控:模型小巧,部署成本低,适合嵌入式或边缘设备。
- 领域定制:你可以用自己的数据训练专属模型,在特定领域(如医疗、金融)达到很高精度。
- 数据隐私:所有处理可在本地完成,无需上传数据到云端,满足严格的隐私要求。
- 透明与学习:其基于统计模型的机制相对透明,非常适合作为学习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 实现流程
- 准备训练数据:收集已分类的新闻文本作为训练样本
- 训练文本分类模型:使用OpenNLP的文档分类器训练模型
- 加载模型并进行分类预测:使用训练好的模型对新文本进行分类
- 评估分类效果:分析分类准确性并优化模型
# 5.3 代码实现
maven引入
<!-- openNLP -->
<dependency>
<groupId>org.apache.opennlp</groupId>
<artifactId>opennlp-tools</artifactId>
<version>1.9.4</version>
</dependency>
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();
}
}
}
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");
}
}
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;
}
}
}
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. 如何为你的需求选择技术
当您面临一个开发需求时,可以遵循以下路径思考
- 需求拆解:我的需求是需要理解文本的结构,还是需要对文本做整体判断,或是需要创造内容?
- 优势领域:它的主要优势在于轻量、可定制且对数据隐私友好。你可以用自己的数据训练专属模型,尤其适合处理金融、医疗等专业领域的文本。此外,作为开源工具,它在成本上也更具灵活性。
- 技术选型:
- 如果需要分词、找实体、分析语法 ——> 首选 OpenNLP 等基础工具包。
- 如果需要文本分类、情感分析、领域问答 ——> OpenNLP 可以胜任,但大语言模型效果通常更好。
- 如果需要开放式对话、内容创作、复杂推理 ——> 首选 大语言模型(LLMs)。
- 考虑约束:如果对数据隐私、计算成本、领域专业性要求极高,OpenNLP 往往是更优解。
- 面临挑战:其挑战主要在于,面对ChatGPT、Claude、Gemini、DeepSeek等大语言模型(LLMs)在语言理解、生成和逻辑推理方面的强大能力,OpenNLP 在执行复杂的语义理解任务时,效果通常不如这些大模型。
虽然当前 AI 领域的焦点在大语言模型上,但 OpenNLP 这类传统工具包在特定场景下依然有不可替代的优势。简单来说,OpenNLP 更像是一套精准的专业工具,而大语言模型则是一个通才。它们并非简单的替代关系,而是可以协作——例如,使用 OpenNLP 进行高效的数据预处理,为大模型提供更干净的结构化输入。
# 7. 参考资料
- Apache OpenNLP官方网站: https://opennlp.apache.org/
- OpenNLP文档: https://opennlp.apache.org/docs/
- OpenNLP GitHub仓库: https://github.com/apache/opennlp
- OpenNLP模型库: https://opennlp.apache.org/models.html