命名实体识别(NER)是自然语言处理(NLP)领域的一项基础技术,用于从非结构化文本中自动提取具有特定含义的实体,如人名、地点、组织和日期等。随着大语言模型(LLM)的兴起,NER 不仅在传统搜索系统中发挥作用,还广泛应用于知识库构建、知识图谱生成和智能问答。
NER 的基本理论
命名实体识别(NER)最早源于信息抽取(Information Extraction)任务,旨在解决文本中"命名实体"的标注问题。根据 MITRE 标准,NER 通常识别三大类实体:
- 实体类:包括 PERSON(人名)、LOCATION(地点)、ORGANIZATION(组织)。
- 时间类:如 DATE(日期)、TIME(时间)。
- 数值类:如 MONEY(货币)、PERCENT(百分比)。
理论上,NER 是序列标注问题:给定句子序列 S = [w1, w2, ..., wn],输出标签序列 L = [l1, l2, ..., ln],其中 li 是 BIO 方案(Begin、Inside、Outside)下的标签。例如,在句子"John Doe lives in New York"中,标签为 [B-PERSON, I-PERSON, O, O, B-LOCATION, I-LOCATION]。
NER 的挑战包括:
- 歧义性:同一词在不同上下文有不同含义(如"Apple"可指水果或公司)。
- 边界检测:准确确定实体的起始和结束位置。
- 多语言支持:中文无词界限,需要字符级处理。
NER 算法的演进
NER 算法从规则-based 到深度学习,再到 LLM-based,经历了显著演进。
-
规则-based 方法:
- 基于正则表达式和词典匹配,早期的 Gazetteer 系统使用预定义实体库。
- 优点:解释性强,无需训练数据。
- 缺点:泛化差,无法处理未知实体。
- 理论基础:有限状态机(FSM),匹配模式如"[A-Z][a-z]+ [A-Z][a-z]+"识别人名。
-
机器学习方法:
- 监督学习 :使用 CRF(Conditional Random Fields)或 HMM(Hidden Markov Model)建模序列概率。
- CRF 最大化 P(L|S) = (1/Z) exp(∑ λk fk(L,S)),其中 fk 是特征函数(如词性、上下文)。
- 深度学习 :BiLSTM-CRF 结合双向 LSTM 捕捉上下文,CRF 优化全局标签。
- BERT 等 Transformer 模型通过预训练嵌入提升准确率,F1 分数可达 90%以上。
- 理论:端到端学习,注意力机制(Attention)捕捉长距离依赖。
- 监督学习 :使用 CRF(Conditional Random Fields)或 HMM(Hidden Markov Model)建模序列概率。
-
LLM-based 方法:
- 利用 GPT 等模型的零样本能力,通过提示(Prompt)指导生成结构化输出。
- 理论:LLM 的上下文理解基于 Transformer 的自注意力,提示工程模拟少样本学习。
- 优点:灵活,支持自定义实体类型,无需标注数据。
- 缺点:幻觉(Hallucination)和 token 限制,需要 schema 验证。
在知识库中,NER 扩展到关系提取(Relation Extraction),使用联合模型(如 LLM 的多步生成)识别实体间关系,如"subject-relation-object"三元组。
NER 在知识库系统中的应用
知识库(如企业文档系统)使用 NER 自动化元数据提取:
- 实体索引:提取实体构建倒排索引,提升搜索精度。
- 知识图谱:实体作为节点,关系作为边,支持推理查询(如"谁是 Apple CEO?")。
- 挑战:处理多模态数据(文本+图像),结合 OCR 或多模态 LLM。
- 益处:提高信息检索的召回率和准确率,理论上基于信息论的熵减少。
基于 Nest.js 和 LLM 的 NER 实现示例
在实践层面,我们使用 Nest.js 构建 API,结合 BullMQ 异步队列和 Vercel AI SDK 调用 LLM。以下辅以关键代码展示实现。
DTO 定义与验证
NerTaskDto
定义输入参数,确保类型安全:
typescript
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, Length, IsOptional, IsNumber, Min, Max } from 'class-validator';
export class NerTaskDto {
@ApiProperty({ description: '文本', example: '示例文本' })
@IsString() @IsNotEmpty() @Length(1, 10000)
text: string;
@ApiProperty({ description: '模型名称', required: false })
@IsOptional() @IsString()
model?: string;
@ApiProperty({ description: '温度', required: false })
@IsOptional() @IsNumber() @Min(0) @Max(2)
temperature?: number;
@ApiProperty({ description: '最大 token', required: false })
@IsOptional() @IsNumber() @Min(1) @Max(50000)
maxTokens?: number;
}
任务创建与控制器
控制器处理请求,创建队列任务:
typescript
@Post('ner')
async ner(@Body() dto: NerTaskDto) {
return this.ok(await this.knowledgeService.createNerTask(dto));
}
服务层入队:
typescript
async createNerTask(dto: NerTaskDto) {
const job = await this.knowledgeQueue.add('ner', { dto });
return { taskId: job.id };
}
NER 执行
使用 Zod 定义 schema 和提示调用 LLM:
typescript
const NerSchema = z.object({
entities: z.array(z.object({ text: z.string(), label: z.enum(["PERSON", "LOCATION", ...]), ... })),
relations: z.array(z.object({ subject: z.string(), relation: z.string(), ... })),
});
const result = await generateObject({
model: this.llmService.createChatModel(dto.model),
prompt: const prompt = `对以下中文或英文文本执行命名实体识别(NER)和关系提取。任务要求:
1. 识别实体,类型包括 PERSON(人名)、LOCATION(地点)、ORGANIZATION(组织)、DATE(日期)或 OTHER(其他)。
2. 为每个实体提供起始索引(start)、结束索引(end)和置信度分数(0-1)。
3. 提取实体之间的关系,指定主语(subject)、关系类型(relation,如 "lives in"、"works at")、宾语(object)和置信度分数(0-1)。
4. 以 JSON 格式返回结果,严格遵循以下 schema:
{
"entities": [
{ "text": string, "label": "PERSON" | "LOCATION" | "ORGANIZATION" | "DATE" | "OTHER", "start": number, "end": number, "confidence": number }
],
"relations": [
{ "subject": string, "relation": string, "object": string, "confidence": number }
]
}
文本: "${dto.text}"
示例输出:
{
"entities": [
{ "text": "John Doe", "label": "PERSON", "start": 0, "end": 8, "confidence": 0.95 },
{ "text": "New York", "label": "LOCATION", "start": 10, "end": 18, "confidence": 0.98 }
],
"relations": [
{ "subject": "John Doe", "relation": "lives in", "object": "New York", "confidence": 0.90 }
]
}
如果文本中没有可识别的实体或关系,返回 {"entities": [], "relations": []}。`,
schema: NerSchema,
temperature: dto.temperature || 0.3,
});
任务查询
getTask
处理状态:
typescript
async getTask(id: string) {
const job = await this.knowledgeQueue.getJob(id);
if (job?.stacktrace?.length > 0) return this.halt(job.failedReason);
if (!job?.returnvalue && job?.finishedOn) await job.retry('completed');
return { ...this.camelcase(job.returnvalue || {}) };
}
总结
NER 是知识库智能化的基石,其理论演进从规则到 LLM 提升了灵活性.