news 2026/5/12 7:22:36

序列标注:NER 与 POS Tagging 实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
序列标注:NER 与 POS Tagging 实战

序列标注:NER 与 POS Tagging 实战

1. 技术分析

1.1 序列标注任务概述

序列标注是为序列中每个元素分配标签的任务:

序列标注任务类型 POS Tagging: 词性标注 NER: 命名实体识别 Chunking: 短语切分 BIOES: 实体边界标注

1.2 标注格式对比

格式描述示例
BIO开始、内部、外部B-PER, I-PER, O
BIOES开始、内部、外部、结束、单字B-PER, I-PER, E-PER, S-PER, O
IOBES同 BIOES同上

1.3 序列标注模型对比

模型特点效果适用场景
HMM概率模型小规模
CRF条件随机场中等规模
BiLSTM-CRF深度学习很高大规模
BERT-CRF预训练+CRF最高大规模

2. 核心功能实现

2.1 CRF 实现

import torch import torch.nn as nn import torch.nn.functional as F class CRF(nn.Module): def __init__(self, num_tags): super().__init__() self.num_tags = num_tags self.transitions = nn.Parameter(torch.randn(num_tags, num_tags)) self.start_transitions = nn.Parameter(torch.randn(num_tags)) self.end_transitions = nn.Parameter(torch.randn(num_tags)) def forward(self, emissions, tags, mask=None): log_likelihood = self._compute_log_likelihood(emissions, tags, mask) return log_likelihood def _compute_log_likelihood(self, emissions, tags, mask): batch_size, seq_len, num_tags = emissions.size() mask = mask if mask is not None else torch.ones(batch_size, seq_len, dtype=torch.bool) score = torch.zeros(batch_size, device=emissions.device) for i in range(seq_len): if i == 0: score += self.start_transitions[tags[:, i]] + emissions[:, i, tags[:, i]] else: score += self.transitions[tags[:, i-1], tags[:, i]] + emissions[:, i, tags[:, i]] score *= mask[:, i] score += self.end_transitions[tags[:, -1]] log_partition = self._compute_log_partition(emissions, mask) return torch.sum(score - log_partition) def _compute_log_partition(self, emissions, mask): batch_size, seq_len, num_tags = emissions.size() alpha = self.start_transitions + emissions[:, 0] for i in range(1, seq_len): alpha = (alpha.unsqueeze(1) + self.transitions + emissions[:, i].unsqueeze(0)).logsumexp(dim=0) return (alpha + self.end_transitions).logsumexp(dim=1).sum() def decode(self, emissions, mask=None): batch_size, seq_len, num_tags = emissions.size() if mask is None: mask = torch.ones(batch_size, seq_len, dtype=torch.bool) scores = torch.zeros(batch_size, num_tags, device=emissions.device) backpointers = torch.zeros(batch_size, seq_len, num_tags, dtype=torch.long, device=emissions.device) scores = self.start_transitions + emissions[:, 0] for i in range(1, seq_len): scores, backpointers[:, i] = (scores.unsqueeze(1) + self.transitions).max(dim=0) scores += emissions[:, i] scores += self.end_transitions best_tags = [] for i in range(batch_size): best_tag = scores[i].argmax().item() best_tags.append([best_tag]) for j in range(seq_len - 1, 0, -1): best_tag = backpointers[i, j, best_tag].item() best_tags[i].append(best_tag) best_tags[i] = best_tags[i][::-1] return best_tags

2.2 BiLSTM-CRF 实现

class BiLSTMCRF(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_tags): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2, bidirectional=True, batch_first=True) self.fc = nn.Linear(hidden_dim, num_tags) self.crf = CRF(num_tags) def forward(self, x, tags=None, mask=None): x = self.embedding(x) x, _ = self.lstm(x) emissions = self.fc(x) if tags is not None: loss = -self.crf(emissions, tags, mask) return loss else: predictions = self.crf.decode(emissions, mask) return predictions class BertCRF(nn.Module): def __init__(self, model_name, num_tags): super().__init__() from transformers import BertModel self.bert = BertModel.from_pretrained(model_name) self.fc = nn.Linear(self.bert.config.hidden_size, num_tags) self.crf = CRF(num_tags) def forward(self, input_ids, attention_mask, tags=None): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) emissions = self.fc(outputs.last_hidden_state) if tags is not None: mask = attention_mask.bool() loss = -self.crf(emissions, tags, mask) return loss else: mask = attention_mask.bool() predictions = self.crf.decode(emissions, mask) return predictions

2.3 序列标注训练与评估

class SequenceLabelingTrainer: def __init__(self, model, optimizer, scheduler): self.model = model self.optimizer = optimizer self.scheduler = scheduler def train_step(self, batch): self.optimizer.zero_grad() input_ids = batch['input_ids'] tags = batch['tags'] mask = batch.get('mask') loss = self.model(input_ids, tags, mask) loss.backward() self.optimizer.step() self.scheduler.step() return loss.item() def evaluate(self, dataloader): self.model.eval() predictions = [] labels = [] with torch.no_grad(): for batch in dataloader: input_ids = batch['input_ids'] tags = batch['tags'] mask = batch.get('mask') preds = self.model(input_ids, mask=mask) for i in range(len(preds)): seq_len = mask[i].sum().item() if mask is not None else len(preds[i]) predictions.extend(preds[i][:seq_len]) labels.extend(tags[i][:seq_len].tolist()) return self._compute_metrics(predictions, labels) def _compute_metrics(self, predictions, labels): from sklearn.metrics import classification_report return classification_report(labels, predictions)

3. 性能对比

3.1 序列标注模型对比

模型F1分数训练时间推理速度数据需求
HMM75%很快
CRF85%
BiLSTM-CRF92%
BERT-CRF96%很慢

3.2 不同数据集的表现

数据集规模CRFBiLSTM-CRFBERT-CRF
CoNLL-200340K89%94%97%
OntoNotes150K92%95%98%
中文NER10K85%90%94%

3.3 标注格式影响

格式边界识别单实体嵌套实体
BIO
BIOES
IOB2

4. 最佳实践

4.1 序列标注模型选择

def select_sequence_labeler(data_size, language): if data_size < 1000: return CRFModel() elif data_size < 10000: return BiLSTMCRF(vocab_size=10000, embedding_dim=100, hidden_dim=200, num_tags=10) else: model_name = 'bert-base-chinese' if language == 'chinese' else 'bert-base-cased' return BertCRF(model_name, num_tags=10) class SequenceLabelerFactory: @staticmethod def create(config): if config['type'] == 'crf': return CRFModel(**config['params']) elif config['type'] == 'bilstm_crf': return BiLSTMCRF(**config['params']) elif config['type'] == 'bert_crf': return BertCRF(**config['params'])

4.2 序列标注数据处理

class SequenceLabelingDataProcessor: def __init__(self, tokenizer, tag_map): self.tokenizer = tokenizer self.tag_map = tag_map self.num_tags = len(tag_map) def process(self, texts, tags): processed = [] for text, tag_sequence in zip(texts, tags): tokens = self.tokenizer.tokenize(text) input_ids = self.tokenizer.convert_tokens_to_ids(tokens) aligned_tags = [] token_idx = 0 for char, tag in zip(text, tag_sequence): if token_idx >= len(tokens): break if tokens[token_idx].startswith('##'): aligned_tags.append(tag) else: aligned_tags.append(tag) if not tokens[token_idx].startswith('##'): token_idx += 1 aligned_tags += ['O'] * (len(tokens) - len(aligned_tags)) processed.append({ 'input_ids': input_ids, 'tags': [self.tag_map[t] for t in aligned_tags], 'mask': [1] * len(input_ids) }) return processed

5. 总结

序列标注是 NLP 重要任务:

  1. CRF:经典模型,适合小规模数据
  2. BiLSTM-CRF:深度学习模型,效果较好
  3. BERT-CRF:预训练+CRF,效果最佳
  4. 标注格式:BIOES 比 BIO 更准确

对比数据如下:

  • BERT-CRF 比 CRF 提升约 10% F1
  • 中文 NER 需要针对分词进行特殊处理
  • 推荐在大规模数据上使用 BERT-CRF
  • 小数据场景 CRF 可能更稳定
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/12 7:21:44

PC出货预测下调背后的产业链传导与半导体行业应对策略

1. 市场预测修正背后的深层逻辑 最近&#xff0c;IDC&#xff08;国际数据公司&#xff09;下调了2013年第一季度全球PC出货量的预测&#xff0c;从原先预期的下滑约8%&#xff0c;调整为下滑近10%。这个看似简单的百分比调整&#xff0c;背后其实是一系列复杂市场动态的连锁反…

作者头像 李华
网站建设 2026/5/12 7:20:57

Python的函数使用介绍

1 跳出循环-breakpython提供了一种方便快捷的跳出循环的方法-break&#xff0c;示例如下&#xff0c;计算未知数字个数的总和&#xff1a;123456789if __name__ "__main__":sum 0while True:num str(input(输入的数字 (或者 "完成"): ))if num 完成:br…

作者头像 李华
网站建设 2026/5/12 7:19:50

秋招编程面试,应届生必备的面试技巧,通过率直接翻倍

文章目录前言一、2026秋招编程面试新趋势&#xff1a;别再用老方法准备&#xff0c;踩坑就出局1.1 八股文不再是核心&#xff0c;底层理解才是硬通货1.2 代码手撕重思路轻结果&#xff0c;工程思维成加分项1.3 项目经历拒绝烂大街&#xff0c;真实落地细节把控是关键二、简历优…

作者头像 李华
网站建设 2026/5/12 7:19:49

工作5年的PHP程序员,转智能体开发半年,薪资翻了2倍

文章目录前言一、PHP程序员的中年危机&#xff1a;不是你不行&#xff0c;是时代变了二、为什么智能体开发是PHP程序员的最优转型方向&#xff1f;1. 门槛最低&#xff0c;上手最快2. 竞争最小&#xff0c;薪资最高3. 前景最好&#xff0c;发展空间最大三、那个转智能体半年薪资…

作者头像 李华
网站建设 2026/5/12 7:15:50

Blitz.js全栈开发框架:基于Next.js的Zero-API数据层实践

1. 项目概述&#xff1a;Blitz.js&#xff0c;一个被低估的全栈开发框架如果你和我一样&#xff0c;在过去几年里一直在用 Next.js 构建全栈应用&#xff0c;那你肯定经历过这种场景&#xff1a;前端页面写得飞快&#xff0c;但一到后端 API 路由、数据库操作、身份验证这些环节…

作者头像 李华
网站建设 2026/5/12 7:15:49

开源智能抓取系统Elsa-OpenClaw:从感知到执行的完整技术栈解析

1. 项目概述&#xff1a;当开源大模型遇上“机械爪”最近在AI和机器人交叉领域&#xff0c;一个名为“Elsa-OpenClaw”的项目引起了我的注意。乍一看&#xff0c;这像是一个将大型语言模型&#xff08;LLM&#xff09;与机械臂末端执行器&#xff08;俗称“机械爪”&#xff09…

作者头像 李华