本文目录导读:

我来详细介绍Java中实现命名实体识别(NER)的几种方法,从简单到复杂。
使用现成NLP库
Stanford CoreNLP
import edu.stanford.nlp.ie.util.RelationTriple;
import edu.stanford.nlp.ling.CoreAnnotations;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.pipeline.*;
import edu.stanford.nlp.util.CoreMap;
import java.util.*;
public class CoreNLPNERExample {
public static void main(String[] args) {
// 设置属性
Properties props = new Properties();
props.setProperty("annotators", "tokenize,ssplit,pos,lemma,ner");
props.setProperty("ner.useSUTime", "false");
// 创建管道
StanfordCoreNLP pipeline = new StanfordCoreNLP(props);
// 示例文本
String text = "苹果公司的CEO蒂姆·库克昨天在纽约发布新款iPhone手机。";
// 处理文本
CoreDocument document = pipeline.processToCoreDocument(text);
// 提取命名实体
for (CoreEntityMention em : document.entityMentions()) {
System.out.println(em.text() + " - " + em.entityType());
}
}
}
Maven依赖:
<dependency>
<groupId>edu.stanford.nlp</groupId>
<artifactId>stanford-corenlp</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>edu.stanford.nlp</groupId>
<artifactId>stanford-corenlp</artifactId>
<version>4.5.0</version>
<classifier>models</classifier>
</dependency>
Apache OpenNLP
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import opennlp.tools.namefind.NameFinderME;
import opennlp.tools.namefind.TokenNameFinderModel;
import opennlp.tools.tokenize.Tokenizer;
import opennlp.tools.tokenize.TokenizerME;
import opennlp.tools.tokenize.TokenizerModel;
import opennlp.tools.util.Span;
public class OpenNLPNERExample {
public static void main(String[] args) throws IOException {
String text = "Bill Gates is the founder of Microsoft Corporation. " +
"He lives in Seattle, Washington.";
// 分词
InputStream tokenModelIn = new FileInputStream("en-token.bin");
TokenizerModel tokenModel = new TokenizerModel(tokenModelIn);
Tokenizer tokenizer = new TokenizerME(tokenModel);
String[] tokens = tokenizer.tokenize(text);
// 命名实体识别
try (InputStream modelIn = new FileInputStream("en-ner-person.bin")) {
TokenNameFinderModel model = new TokenNameFinderModel(modelIn);
NameFinderME nameFinder = new NameFinderME(model);
Span[] nameSpans = nameFinder.find(tokens);
for (Span span : nameSpans) {
StringBuilder entity = new StringBuilder();
for (int i = span.getStart(); i < span.getEnd(); i++) {
entity.append(tokens[i]).append(" ");
}
System.out.println(entity.toString().trim() + " - " + span.getType());
}
}
}
}
Maven依赖:
<dependency>
<groupId>org.apache.opennlp</groupId>
<artifactId>opennlp-tools</artifactId>
<version>2.1.0</version>
</dependency>
基于规则的方法
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RuleBasedNER {
// 定义实体规则
private static final Map<String, Pattern> ENTITY_PATTERNS = new HashMap<>();
static {
// 人名模式
ENTITY_PATTERNS.put("PERSON",
Pattern.compile("([张王李赵刘陈杨黄吴周徐马胡朱郭何罗高林郑孙梁谢宋唐许韩冯邓曹彭曾肖田董潘袁蔡蒋余于杜叶程魏苏吕丁贾薛姜])[\\u4e00-\\u9fa5]{1,2}"));
// 日期模式
ENTITY_PATTERNS.put("DATE",
Pattern.compile("(\\d{4}年\\d{1,2}月\\d{1,2}日|\\d{4}-\\d{2}-\\d{2}|\\d{1,2}月\\d{1,2}日)"));
// 电话号码模式
ENTITY_PATTERNS.put("PHONE",
Pattern.compile("(1[3-9]\\d{9}|\\d{3,4}-\\d{7,8})"));
// 邮箱模式
ENTITY_PATTERNS.put("EMAIL",
Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"));
// 地址模式(简单版本)
ENTITY_PATTERNS.put("LOCATION",
Pattern.compile("([\\u4e00-\\u9fa5]{2,5}(?:省|市|区|县|镇|乡|村|路|街|道))"));
}
public static class NamedEntity {
private String text;
private String type;
private int start;
private int end;
public NamedEntity(String text, String type, int start, int end) {
this.text = text;
this.type = type;
this.start = start;
this.end = end;
}
@Override
public String toString() {
return String.format("实体: '%s', 类型: %s, 位置: [%d, %d]",
text, type, start, end);
}
}
public List<NamedEntity> extractEntities(String text) {
List<NamedEntity> entities = new ArrayList<>();
for (Map.Entry<String, Pattern> entry : ENTITY_PATTERNS.entrySet()) {
String type = entry.getKey();
Pattern pattern = entry.getValue();
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
entities.add(new NamedEntity(
matcher.group(),
type,
matcher.start(),
matcher.end()
));
}
}
// 按出现位置排序
Collections.sort(entities, (e1, e2) -> Integer.compare(e1.start, e2.start));
return entities;
}
public static void main(String[] args) {
RuleBasedNER ner = new RuleBasedNER();
String text = "你好,我是张三,我的邮箱是zhangsan@example.com,电话是13800138000,住在北京市朝阳区。";
List<NamedEntity> entities = ner.extractEntities(text);
System.out.println("文本: " + text);
System.out.println("识别到的命名实体:");
for (NamedEntity entity : entities) {
System.out.println(entity);
}
}
}
使用深度学习模型(Hugging Face Transformers)
import ai.djl.Application;
import ai.djl.ModelException;
import ai.djl.inference.Predictor;
import ai.djl.modality.nlp.bert.BertTokenizer;
import ai.djl.modality.nlp.ner.NamedEntityRecognition;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import java.io.IOException;
public class TransformersNERExample {
public static void main(String[] args) throws IOException, ModelException {
// 加载预训练模型
Criteria<String, NamedEntityRecognition> criteria =
Criteria.builder()
.optApplication(Application.NLP.NAMED_ENTITY_RECOGNITION)
.setTypes(String.class, NamedEntityRecognition.class)
.optProgress(new ProgressBar())
.build();
try (ZooModel<String, NamedEntityRecognition> model = ModelZoo.loadModel(criteria);
Predictor<String, NamedEntityRecognition> predictor = model.newPredictor()) {
// 示例文本
String text = "Barack Obama was born in Hawaii. He was the 44th president of the United States.";
// 执行命名实体识别
NamedEntityRecognition result = predictor.predict(text);
// 输出结果
System.out.println("文本: " + text);
System.out.println("\n命名实体识别结果:");
for (NamedEntityRecognition.Entity entity : result.getEntities()) {
System.out.printf("%-20s 类型: %-10s 置信度: %.4f%n",
entity.getText(), entity.getType(), entity.getProbability());
}
}
}
}
综合NER服务类
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class NERService {
private RuleBasedNER ruleBasedNER;
private Map<String, List<String>> customDictionary;
private Set<String> stopWords;
public NERService() {
this.ruleBasedNER = new RuleBasedNER();
this.customDictionary = new ConcurrentHashMap<>();
this.stopWords = new HashSet<>(Arrays.asList("的", "了", "是", "在", "和"));
// 加载默认字典
loadDefaultDictionary();
}
private void loadDefaultDictionary() {
// 人名
customDictionary.put("PERSON", Arrays.asList("张三", "李四", "王五"));
// 地名
customDictionary.put("LOCATION", Arrays.asList("北京", "上海", "广州"));
// 组织机构
customDictionary.put("ORGANIZATION", Arrays.asList("阿里巴巴", "腾讯", "百度"));
}
public List<NamedEntity> extractAll(String text) {
List<NamedEntity> allEntities = new ArrayList<>();
// 1. 基于规则的识别
allEntities.addAll(ruleBasedNER.extractEntities(text));
// 2. 基于词典的匹配
allEntities.addAll(matchDictionary(text));
// 3. 去除重叠和重复
allEntities = removeOverlaps(allEntities);
// 4. 按位置排序
Collections.sort(allEntities, (e1, e2) -> Integer.compare(e1.start, e2.start));
return allEntities;
}
private List<NamedEntity> matchDictionary(String text) {
List<NamedEntity> entities = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : customDictionary.entrySet()) {
String type = entry.getKey();
for (String entity : entry.getValue()) {
int index = 0;
while ((index = text.indexOf(entity, index)) != -1) {
entities.add(new NamedEntity(entity, type, index, index + entity.length()));
index += entity.length();
}
}
}
return entities;
}
private List<NamedEntity> removeOverlaps(List<NamedEntity> entities) {
List<NamedEntity> result = new ArrayList<>();
for (NamedEntity entity : entities) {
boolean hasOverlap = false;
for (NamedEntity existing : result) {
if (isOverlapping(entity, existing)) {
hasOverlap = true;
// 保留较长的实体
if (entity.text.length() > existing.text.length()) {
result.remove(existing);
result.add(entity);
}
break;
}
}
if (!hasOverlap) {
result.add(entity);
}
}
return result;
}
private boolean isOverlapping(NamedEntity a, NamedEntity b) {
return Math.max(a.start, b.start) < Math.min(a.end, b.end);
}
public void addCustomEntity(String type, String entity) {
customDictionary.computeIfAbsent(type, k -> new ArrayList<>()).add(entity);
}
// 测试方法
public static void main(String[] args) {
NERService nerService = new NERService();
// 添加自定义实体
nerService.addCustomEntity("PRODUCT", "iPhone");
nerService.addCustomEntity("PRODUCT", "微信");
String text = "张三是阿里巴巴的员工,他的电话是13800138000,住在北京,他使用的iPhone手机很好。";
List<NamedEntity> entities = nerService.extractAll(text);
System.out.println("文本: " + text);
System.out.println("\n识别结果:");
for (NamedEntity entity : entities) {
System.out.println(entity);
}
}
}
性能优化建议
// 缓存优化
public class NEROfficialCache {
private final Map<String, List<NamedEntity>> cache;
private final LRUCache<String, List<NamedEntity>> recentCache;
private final NERService nerService;
public NEROfficialCache() {
this.cache = new ConcurrentHashMap<>();
this.recentCache = new LRUCache<>(1000); // 最近1000个结果
this.nerService = new NERService();
}
public List<NamedEntity> extractBatch(List<String> texts) {
List<NamedEntity> results = new ArrayList<>();
for (String text : texts) {
// 检查缓存
if (cache.containsKey(text)) {
results.addAll(cache.get(text));
continue;
}
// 检查最近缓存
if (recentCache.containsKey(text)) {
results.addAll(recentCache.get(text));
continue;
}
// 执行NER
List<NamedEntity> entities = nerService.extractAll(text);
results.addAll(entities);
// 更新缓存
cache.put(text, entities);
recentCache.put(text, entities);
}
return results;
}
}
使用建议
- 简单场景:使用基于规则的方法
- 中文场景:推荐Stanford CoreNLP或HanLP
- 英文场景:OpenNLP或SpaCy
- 高精度要求:使用微调的BERT模型
- 实时系统:优先考虑规则方法和轻量级模型
选择哪种方法取决于你的具体需求、数据量、精度要求和系统资源限制。