如何高效标注100k+N2C2数据集用于BERT-NER?求实现资源
针对N2C2数据集的100k+规模BERT-NER高效标注方案
一、高效标注核心策略
- 半监督预标注先行:用N2C2已标注子集(比如20k规模)训练基础BERT-NER模型,对剩余未标注数据批量预标注。过滤置信度>0.9的结果直接保留,仅对低置信度(<0.7)或边界模糊的样本做人工修正,可减少70%以上的纯人工标注量。
- 主动学习迭代优化:优先筛选模型最不确定的样本(如实体置信度接近0.5、边界歧义的文本)进行标注,每完成一批标注就重新训练模型,再迭代筛选下一批高价值样本,最大化标注效率。
- 医疗规则辅助过滤:针对N2C2的医疗文本特性,编写正则表达式匹配常见实体(如ICD疾病代码、药物通用名、诊疗操作术语),先做规则预标注,再结合模型结果交叉验证,降低人工标注成本。
- 本地批量标注工具:使用LabelStudio(本地部署)或Prodigy,配置自定义NER标注模板,支持快捷键批量确认/修正预标注结果,避免在线工具的数据安全风险。
二、核心实现代码示例
1. 基于N2C2子集训练基础BERT-NER模型
from transformers import BertTokenizer, BertForTokenClassification, Trainer, TrainingArguments import datasets # 加载N2C2标注子集(需转换为JSON格式,包含text和labels字段) dataset = datasets.load_dataset("json", data_files="n2c2_labeled_subset.json") # 定义N2C2实体对应的BIO标签列表 label_list = ["O", "B-DISEASE", "I-DISEASE", "B-DRUG", "I-DRUG", "B-TREATMENT", "I-TREATMENT"] tokenizer = BertTokenizer.from_pretrained("bert-base-uncased") def tokenize_and_align_labels(examples): tokenized_inputs = tokenizer(examples["text"], truncation=True, padding="max_length", max_length=128) labels = [] for i, label in enumerate(examples["labels"]): word_ids = tokenized_inputs.word_ids(batch_index=i) previous_word_idx = None label_ids = [] for word_idx in word_ids: if word_idx is None: label_ids.append(-100) # padding标签忽略 elif word_idx != previous_word_idx: label_ids.append(label_list.index(label[word_idx])) else: # 非首词的实体标签需保持I-前缀 current_label = label_list.index(label[word_idx]) label_ids.append(current_label if label_list[current_label].startswith("I-") else -100) previous_word_idx = word_idx labels.append(label_ids) tokenized_inputs["labels"] = labels return tokenized_inputs tokenized_datasets = dataset.map(tokenize_and_align_labels, batched=True) model = BertForTokenClassification.from_pretrained("bert-base-uncased", num_labels=len(label_list)) training_args = TrainingArguments( output_dir="./n2c2_bert_ner", per_device_train_batch_size=16, num_train_epochs=3, logging_dir="./logs", logging_steps=100, evaluation_strategy="epoch", save_strategy="epoch" ) trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"] ) trainer.train()
2. 批量预标注未标注数据
import torch import json from transformers import pipeline # 加载训练好的模型和分词器 ner_pipeline = pipeline("ner", model="./n2c2_bert_ner/checkpoint-xxx", tokenizer=tokenizer, aggregation_strategy="simple") # 读取未标注文本 with open("n2c2_unlabeled_data.txt", "r", encoding="utf-8") as f: unlabeled_texts = [line.strip() for line in f if line.strip()] prelabeled_results = [] for text in unlabeled_texts: ner_results = ner_pipeline(text) # 转换为BIO格式标签(按词拆分) words = text.split() bio_labels = ["O"] * len(words) for res in ner_results: # 匹配实体对应的词位置(简化处理,可根据实际文本优化) char_start, char_end = res["start"], res["end"] word_start = len(text[:char_start].split()) word_end = len(text[:char_end].split()) entity_type = res["entity_group"] bio_labels[word_start] = f"B-{entity_type}" for idx in range(word_start + 1, word_end): bio_labels[idx] = f"I-{entity_type}" prelabeled_results.append({"text": text, "labels": bio_labels}) # 保存预标注结果 with open("n2c2_prelabeled_data.json", "w", encoding="utf-8") as f: json.dump(prelabeled_results, f, indent=2, ensure_ascii=False)
3. 主动学习筛选高不确定性样本
import numpy as np def calculate_uncertainty(model, tokenizer, texts): model.eval() uncertainty_scores = [] with torch.no_grad(): for text in texts: inputs = tokenizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128) outputs = model(**inputs) logits = outputs.logits # 计算每个token的熵,取平均值作为样本不确定性 probs = torch.softmax(logits, dim=-1) entropy = -torch.sum(probs * torch.log(probs + 1e-10), dim=-1) # 忽略padding部分的熵 valid_entropy = entropy[inputs["attention_mask"].squeeze() == 1] avg_entropy = torch.mean(valid_entropy).item() uncertainty_scores.append(avg_entropy) return uncertainty_scores # 获取所有未标注样本的不确定性分数 uncertainty_scores = calculate_uncertainty(model, tokenizer, unlabeled_texts) # 筛选top 1000个最不确定的样本用于人工标注 top_k = 1000 top_uncertain_indices = np.argsort(uncertainty_scores)[-top_k:] top_uncertain_texts = [unlabeled_texts[i] for i in top_uncertain_indices] # 保存待标注样本 with open("n2c2_high_priority_texts.txt", "w", encoding="utf-8") as f: for text in top_uncertain_texts: f.write(text + "\n")
三、实用教程与资源
- N2C2数据格式适配:参考N2C2官方数据说明,将原始XML/CSV格式转换为BIO格式,重点处理医疗文本中的长实体、嵌套实体标注逻辑。
- Hugging Face NER流程:学习官方TokenClassification任务教程,掌握数据预处理、模型微调、评估的完整流程,直接适配N2C2的实体类型即可。
- 主动学习落地:使用
modAL库实现主动学习的自动化样本筛选,结合模型迭代训练,进一步提升标注效率。 - 标注工具使用:参考LabelStudio官方NER标注模块教程,配置自定义实体类型,导入预标注结果进行批量修正。
内容的提问来源于stack exchange,提问作者Muh. Rayi
相关产品推荐
相关产品推荐

