news 2026/6/2 10:52:58

小白必看!用Qwen2.5-0.5B实现中文命名实体识别全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小白必看!用Qwen2.5-0.5B实现中文命名实体识别全流程

小白必看!用Qwen2.5-0.5B实现中文命名实体识别全流程

1. 引言:为什么选择Qwen2.5-0.5B做NER任务?

在自然语言处理(NLP)领域,命名实体识别(Named Entity Recognition, NER)是一项基础而关键的任务,广泛应用于信息抽取、知识图谱构建、智能客服等场景。传统方法依赖于标注数据训练BiLSTM-CRF或BERT类模型,但随着大语言模型(LLM)的发展,我们可以通过指令微调(Instruction Tuning)的方式,让通用大模型学会结构化输出能力,从而高效完成NER任务。

本文面向初学者,手把手带你使用阿里开源的轻量级大模型Qwen2.5-0.5B-Instruct,基于中文NER数据集 CLUENER2020 实现从环境准备、数据预处理、全参数微调到模型测试的完整流程。该方案具备以下优势:

  • ✅ 模型体积小(仅0.5B),适合单卡甚至消费级显卡训练
  • ✅ 支持多语言和结构化输出(JSON格式),天然适配NER任务
  • ✅ 提供网页推理接口,部署便捷
  • ✅ 全程代码可复现,附带详细解析

通过本教程,你将掌握如何将一个通用大模型“教会”执行专业NLP任务的核心思路与工程实践。


2. 技术背景与数据准备

2.1 Qwen2.5-0.5B模型特性解析

Qwen2.5-0.5B-Instruct是通义千问系列中最小的指令调优版本,专为轻量化部署和快速迭代设计。其核心能力包括:

  • 支持最长128K上下文输入,适用于长文本理解
  • 生成长度可达8K tokens,满足复杂结构化输出需求
  • 内置对JSON等结构化格式的强解析能力
  • 支持超过29种语言,其中中文表现尤为出色
  • 经过高质量指令微调,能准确理解系统提示(system prompt)

这些特性使其非常适合用于需要精确提取并格式化输出实体信息的NER任务。

📌 官方ModelScope地址:https://modelscope.cn/models/Qwen/Qwen2.5-0.5B-Instruct

2.2 数据集介绍:CLUENER2020 中文命名实体识别基准

我们采用业界广泛使用的CLUENER2020数据集进行实验,包含10类中文实体标签:

标签类别示例
address北京、上海
book《三体》
company阿里巴巴
game英雄联盟
government教育部
movie流浪地球
name张三
organization联合国儿童基金会
position总经理
scene故宫博物院

原始数据格式如下:

{ "text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。", "label": { "name": {"叶老桂": [[9, 11]]}, "company": {"浙商银行": [[0, 3]]} } }

由于我们更关注实体内容而非位置索引,因此对数据进行了简化转换,仅保留实体名称列表。

2.3 数据预处理:统一为结构化JSON输出格式

为了适配大模型的生成式NER任务,我们将原始数据转换为如下格式:

{ "text": "彭小军认为,国内银行现在走的是台湾的发卡模式", "label": { "address": ["台湾"], "name": ["彭小军"] } }

对应的Python转换脚本如下:

import json def trans(file_path, save_path): with open(save_path, "a", encoding="utf-8") as w: with open(file_path, "r", encoding="utf-8") as r: for line in r: line = json.loads(line) text = line['text'] label = line['label'] trans_label = {} for key, items in label.items(): items = list(items.keys()) # 只保留实体名 trans_label[key] = items trans = { "text": text, "label": trans_label } line = json.dumps(trans, ensure_ascii=False) w.write(line + "\n") w.flush() if __name__ == '__main__': trans("ner_data_origin/train.json", "ner_data/train.json") trans("ner_data_origin/dev.json", "ner_data/val.json")

2.4 Token分布分析:确定最大序列长度

为合理设置输入输出长度,我们统计了训练集的Token分布情况:

from transformers import AutoTokenizer import json import numpy as np def get_token_distribution(file_path, tokenizer): input_tokens, output_tokens = [], [] with open(file_path, "r", encoding="utf-8") as f: for line in f: data = json.loads(line) text = data["text"] label = json.dumps(data["label"], ensure_ascii=False) input_tokens.append(len(tokenizer(text).input_ids)) output_tokens.append(len(tokenizer(label).input_ids)) return np.mean(input_tokens), np.max(input_tokens), np.mean(output_tokens), np.max(output_tokens) # 加载 tokenizer tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct", trust_remote_code=True) i_avg, i_max, o_avg, o_max = get_token_distribution("ner_data/train.json", tokenizer) print(f"Input - Avg: {i_avg:.1f}, Max: {i_max}") print(f"Output - Avg: {o_avg:.1f}, Max: {o_max}")

结果表明: - 输入最大约50个Token - 输出最大约70个Token

据此设定:

max_source_length = 50 # 输入文本最大长度 max_target_length = 140 # 输出标签最大长度(预留冗余)

3. 模型微调:从零构建NER数据集与训练流程

3.1 自定义Dataset类:构造对话式训练样本

我们利用Qwen的apply_chat_template方法,将NER任务构造成“系统指令+用户提问”的对话形式,激发其结构化输出能力。

# ner_dataset.py from torch.utils.data import Dataset import torch import json import numpy as np class NerDataset(Dataset): def __init__(self, data_path, tokenizer, max_source_length, max_target_length): super().__init__() self.tokenizer = tokenizer self.max_source_length = max_source_length self.max_target_length = max_target_length self.max_seq_length = max_source_length + max_target_length self.data = [] if data_path: with open(data_path, "r", encoding='utf-8') as f: for line in f: if not line.strip(): continue item = json.loads(line) self.data.append({ "text": item["text"], "label": json.dumps(item["label"], ensure_ascii=False) }) print(f"Loaded {len(self.data)} samples.") def preprocess(self, text, label): messages = [ {"role": "system", "content": "你的任务是做NER任务提取,根据用户输入提取出完整的实体信息,并以JSON格式输出。"}, {"role": "user", "content": text}, {"role": "assistant", "content": label} ] # 构造prompt prompt = self.tokenizer.apply_chat_template( messages[:-1], tokenize=False, add_generation_prompt=True ) response = label # 编码输入 input_enc = self.tokenizer( prompt, truncation=True, max_length=self.max_source_length, padding="max_length", return_tensors="pt", add_special_tokens=False ) # 编码输出 output_enc = self.tokenizer( response, truncation=True, max_length=self.max_target_length, padding="max_length", return_tensors="pt", add_special_tokens=False ) input_ids = input_enc["input_ids"][0].tolist() attention_mask = input_enc["attention_mask"][0].tolist() labels = [-100] * len(input_ids) + output_enc["input_ids"][0].tolist() return { "input_ids": torch.LongTensor(input_ids), "attention_mask": torch.LongTensor(attention_mask), "labels": torch.LongTensor(labels) } def __getitem__(self, index): return self.preprocess(**self.data[index]) def __len__(self): return len(self.data)

🔍 关键点说明: - 使用-100填充非输出部分的labels,确保损失函数只计算Assistant回复部分 -add_generation_prompt=True自动添加<|im_start|>assistant\n触发生成

3.2 训练脚本详解:全参数微调策略

# train.py import torch from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter from transformers import AutoModelForCausalLM, AutoTokenizer from ner_dataset import NerDataset from tqdm import tqdm import time, sys def train_model(model, train_loader, val_loader, optimizer, device, num_epochs, model_output_dir, writer): batch_step = 0 for epoch in range(num_epochs): model.train() start_time = time.time() for idx, batch in enumerate(tqdm(train_loader, desc=f"Train Epoch [{epoch+1}/{num_epochs}]")): input_ids = batch['input_ids'].to(device) attention_mask = batch['attention_mask'].to(device) labels = batch['labels'].to(device) optimizer.zero_grad() outputs = model( input_ids=input_ids, attention_mask=attention_mask, labels=labels ) loss = outputs.loss loss.backward() optimizer.step() writer.add_scalar('Loss/Train', loss.item(), batch_step) batch_step += 1 if idx % 100 == 0: avg_time = (time.time() - start_time) / (idx + 1) print(f"Step {idx}, Loss: {loss:.4f}, Time per step: {avg_time:.2f}s") # Validation model.eval() val_loss = 0.0 with torch.no_grad(): for batch in tqdm(val_loader, desc="Validation"): input_ids = batch['input_ids'].to(device) attention_mask = batch['attention_mask'].to(device) labels = batch['labels'].to(device) outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels) val_loss += outputs.loss.item() val_loss /= len(val_loader) writer.add_scalar('Loss/Val', val_loss, epoch) print(f"Epoch {epoch+1} | Val Loss: {val_loss:.4f}") # Save checkpoint model.save_pretrained(model_output_dir) print(f"Model saved to {model_output_dir}") def main(): model_name = "Qwen/Qwen2.5-0.5B-Instruct" train_path = "ner_data/train.json" val_path = "ner_data/val.json" output_dir = "output_ner" log_dir = "logs" device = torch.device("cuda" if torch.cuda.is_available() else "cpu") tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True) # Dataset train_set = NerDataset(train_path, tokenizer, max_source_length=50, max_target_length=140) val_set = NerDataset(val_path, tokenizer, max_source_length=50, max_target_length=140) train_loader = DataLoader(train_set, batch_size=16, shuffle=True, num_workers=4) val_loader = DataLoader(val_set, batch_size=16, shuffle=False) # Optimizer & Writer optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4) writer = SummaryWriter(log_dir) model.to(device) train_model( model=model, train_loader=train_loader, val_loader=val_loader, optimizer=optimizer, device=device, num_epochs=30, model_output_dir=output_dir, writer=writer ) writer.close() if __name__ == '__main__': main()

⚙️ 训练建议: - 显存不足时可降低batch_size至8或4 - 可加入学习率调度器(如CosineAnnealingLR) - 监控TensorBoard曲线防止过拟合


4. 模型测试与效果验证

4.1 推理脚本编写

# test.py from transformers import AutoModelForCausalLM, AutoTokenizer import torch def predict(text, model, tokenizer, device): messages = [ {"role": "system", "content": "你的任务是做NER任务提取,根据用户输入提取出完整的实体信息,并以JSON格式输出。"}, {"role": "user", "content": text} ] prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = tokenizer(prompt, return_tensors="pt").to(device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=140, do_sample=False, # 贪心解码保证稳定性 top_k=1 ) # 截取新生成的部分 new_tokens = outputs[0][inputs['input_ids'].shape[-1]:] response = tokenizer.decode(new_tokens, skip_special_tokens=True) return response # 加载模型 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = AutoModelForCausalLM.from_pretrained("output_ner", trust_remote_code=True).to(device) tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct", trust_remote_code=True) # 测试案例 test_cases = [ "三星WCG2011北京赛区魔兽争霸3最终名次", "新华网孟买3月10日电(记者聂云)印度国防部10日说,印度政府当天批准", "证券时报记者肖渔" ] for case in test_cases: result = predict(case, model, tokenizer, device) print(f"输入: {case}") print(f"输出: {result}\n")

4.2 实际输出示例

输入: 新华网孟买3月10日电(记者聂云)印度国防部10日说,印度政府当天批准 输出: {"organization": ["新华网"], "address": ["孟买"], "position": ["记者"], "name": ["聂云"], "government": ["印度国防部", "印度政府"]}

✅ 模型成功识别出媒体、地点、职位、人名和政府机构等多种实体,且输出为标准JSON格式,便于后续系统集成。


5. 总结

本文完整展示了如何使用Qwen2.5-0.5B-Instruct实现中文命名实体识别任务的端到端流程:

  1. 数据准备:选用CLUENER2020数据集并转换为结构化输出格式;
  2. 模型适配:通过对话模板引导模型理解NER任务意图;
  3. 训练实现:基于PyTorch和HuggingFace Transformers完成全参数微调;
  4. 推理验证:模型能够稳定输出JSON格式的实体识别结果。

💡核心价值总结: - 利用大模型强大的泛化能力和结构化输出能力,简化传统NER流水线 - 0.5B小模型即可胜任专业NLP任务,适合资源受限场景 - 指令微调范式易于迁移到其他信息抽取任务(如关系抽取、事件识别)

未来可进一步探索: - 结合LoRA进行高效微调,节省显存 - 引入Few-shot Prompt提升少样本性能 - 部署为API服务或Web应用


💡获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/21 16:38:27

AI人脸隐私卫士处理前后对比:效果评估实战方法

AI人脸隐私卫士处理前后对比&#xff1a;效果评估实战方法 1. 引言&#xff1a;AI 人脸隐私卫士的现实需求与技术背景 在社交媒体、公共数据集和企业文档日益普及的今天&#xff0c;图像中的人脸信息泄露风险急剧上升。一张看似普通的合照&#xff0c;可能包含多位未授权出镜…

作者头像 李华
网站建设 2026/6/1 6:48:41

深度学习计算机毕设之基于python-CNN卷积神经网络训练识别马路是否有坑洼基于python的卷神经网络训练识别马路是否有坑洼

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/5/29 4:25:36

MediaPipe Pose模型优化:减少误检的实用技巧

MediaPipe Pose模型优化&#xff1a;减少误检的实用技巧 1. 背景与挑战&#xff1a;AI人体骨骼关键点检测中的误检问题 随着计算机视觉技术的发展&#xff0c;人体姿态估计&#xff08;Human Pose Estimation&#xff09;已成为智能健身、动作捕捉、虚拟试衣和人机交互等场景…

作者头像 李华
网站建设 2026/5/30 21:23:59

手部关键点检测优化:MediaPipe Hands算法改进

手部关键点检测优化&#xff1a;MediaPipe Hands算法改进 1. 引言&#xff1a;AI 手势识别与追踪的工程挑战 随着人机交互技术的发展&#xff0c;手势识别正逐步成为智能设备、虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;和智能家居等场景中的核…

作者头像 李华
网站建设 2026/5/30 7:40:16

4.37 父页面检索与整合检索器:多层级检索,提升RAG召回率

4.37 父页面检索与整合检索器:多层级检索,提升RAG召回率 引言 父页面检索与整合检索器可以提升RAG召回率。本文演示多层级检索方法。 一、多层级检索 1.1 检索策略 # 多层级检索 def multi_level_retrieval():"""多层级检索"""print(&quo…

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

MediaPipe Pose与ROS集成:机器人视觉感知系统部署教程

MediaPipe Pose与ROS集成&#xff1a;机器人视觉感知系统部署教程 1. 引言 1.1 学习目标 本文将带你从零开始&#xff0c;完成 MediaPipe Pose 与 ROS&#xff08;Robot Operating System&#xff09; 的深度集成&#xff0c;构建一套可用于服务机器人、人机交互或行为识别场…

作者头像 李华