ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BERT三元组抽取毕设实战:从NER到RE的完整工程解析

BERT三元组抽取毕设实战:从NER到RE的完整工程解析 简介本资源面向计算机相关专业学生与NLP入门学习者提供一套基于BERT实现关系三元组抽取的完整项目可用于毕业设计、课程设计、期末大作业或项目实战练习。项目围绕命名实体识别与关系抽取两条主线展开包含模型定义、数据加载、训练与预测等核心模块并配有中文预训练模型与多份数据集配置便于直接复现与二次修改。压缩包共56个文件以24个txt数据文件、16个py源码、14个json配置及2个md说明为主整体约3.82MB结构清晰、模块划分明确。目前已有455人学习下载。读者可从中获得可运行的源码、配套数据集与项目说明理解三元组抽取的完整流程与实现思路并在此基础上调整模型或扩展功能适合作为NLP方向的学习与实战参考。1. 从一份能跑通的 BERT 三元组抽取毕设说起很多做课程设计或毕业设计的同学选题卡在“关系抽取”这一步知道要用 BERT也知道要抽 (头实体, 关系, 尾实体) 三元组但真到动手时数据怎么标注、NER 和 RE 两个任务怎么串、预测结果怎么解码成三元组全是黑匣子。这份资源就是一份已经跑通的完整工程基于chinese-bert-wwm-ext把命名实体识别和关系分类拆成两条流水线附带duie和dgre两套数据集源码、权重占位、配置文件、预测脚本一应俱全。它适合正在做毕设、课设或者想拿一个真实中文信息抽取项目练手的人。下面我按“先看懂结构、再跑通训练、最后避坑”的顺序把这份源码拆开讲一遍。2. 工程结构与数据流先搞清楚 ner_main 和 re_main 怎么接力拿到压缩包别急着python ner_main.py先把目录和数据流理顺。这份工程的核心设计是“两阶段抽取”第一阶段用 BERT 做 NER把句子里的实体边界和类型识别出来第二阶段把实体两两配对用 BERT 做关系分类判断这对实体之间属于哪种预定义关系。两阶段共享同一个model_hub/chinese-bert-wwm-ext底座但各自有独立的ner_args.json和re_args.json互不干扰。2.1 目录分层与两套数据集的对应关系解压后你会看到data下分dgre和duie两个目录每个目录里又有re_data、ner_data、ori_data三层。ori_data是原始标注ner_data是转成 BERT 序列标注格式后的数据re_data是转成关系分类格式后的数据。process.py负责这个转换data_loader.py负责读成 Dataset。Project_upload_all是另一份结构相同的备份实际跑的时候认准根目录下的ner_main.py、re_main.py、predict.py即可。文件/目录作用跑之前要不要动model_hub/chinese-bert-wwm-ext预训练权重占位目录需要放入真实权重ner_args.json/re_args.json两阶段的超参配置按显存改 batch_sizeprocess.py原始数据转训练格式换数据集时改data_loader.py构造 Dataset 和 collate一般不动model.pyBERT 分类头定义换底座时改predict.py加载权重做三元组解码推理入口placeholder.txt说明权重目录是空的这是这类资源最常见的处理方式——权重太大不方便直接打包。你需要自己去下载chinese-bert-wwm-ext的 pytorch 版本把config.json、pytorch_model.bin、vocab.txt放进去。注意目录名必须和配置里写的一致否则from_pretrained会直接报路径找不到。2.2 从原始标注到 BERT 输入process.py 干了什么process.py是整条流水线的起点。以 NER 为例原始数据通常是“文本 实体列表”的 JSON转换后要变成每个字对应一个 BIO 标签的序列。关系数据则要变成“句子 头实体位置 尾实体位置 关系类别”的四元组。下面这段是我按这份工程的数据格式还原的核心转换逻辑你可以对照process.py里的实现看import json def convert_ner(raw_sample, label2id): 把一条原始样本转成 BERT 序列标注格式 text raw_sample[text] # 初始化全 O 标签长度对齐字符数 labels [O] * len(text) for ent in raw_sample[entities]: start, end, etype ent[start], ent[end], ent[type] # 首字标 B-其余标 I-这是 BIO 标注的硬约定 labels[start] B- etype for i in range(start 1, end): labels[i] I- etype # 转成 id未知标签直接抛错避免静默丢数据 label_ids [label2id[l] for l in labels] return {text: text, label_ids: label_ids} def convert_re(raw_sample, rel2id, tokenizer, max_len128): 把实体对转成关系分类输入用 [SEP] 拼接头尾实体上下文 text raw_sample[text] head, tail, rel raw_sample[head], raw_sample[tail], raw_sample[relation] # 常见做法是把头实体和尾实体用特殊标记包起来这里用位置索引 encoding tokenizer( text, max_lengthmax_len, paddingmax_length, truncationTrue, return_tensorspt ) return { input_ids: encoding[input_ids], attention_mask: encoding[attention_mask], head_start: head[start], tail_start: tail[start], label: rel2id[rel] }逻辑说明NER 转换的关键是标签对齐labels长度必须严格等于len(text)多一个少一个都会在DataLoader里报维度不匹配。关系转换里head_start和tail_start是给模型做位置感知用的常见做法是在 BERT 输出上取对应位置的 hidden state 拼接而不是只取[CLS]。参数上max_len128对大多数中文短句够用长文本要改大但注意显存会线性涨。2.3 两阶段如何串成一次完整预测predict.py是最终入口它的流程是先跑 NER 拿到所有实体再对实体做笛卡尔积配对逐对跑 RE最后过滤掉“无关系”的配对输出三元组。这里有个容易忽略的点——实体配对数量是平方级增长的一句话有 10 个实体就是 90 对推理耗时主要花在 RE 阶段。常见优化是先按实体类型做粗筛比如“人物”和“地点”才可能构成“出生地”关系不满足类型约束的直接跳过能砍掉一大半计算量。3. 把训练跑起来参数、显存与断点续训结构看懂之后真正动手跑训练。这份工程的训练脚本是ner_main.py和re_main.py两者结构几乎对称都是“读配置 → 建模型 → 建 DataLoader → 训练循环 → 存 checkpoint”。跑之前先把ner_args.json和re_args.json过一遍这两个文件决定了你能不能在自己的机器上跑起来。3.1 关键参数逐项说明配置文件里参数不少但真正影响能否跑通的就那么几个。下面按重要性排一下参数典型值作用调错会怎样batch_size16 / 32每批样本数太大直接 OOMmax_seq_len128截断长度太小截掉实体learning_rate2e-5BERT 微调学习率太大不收敛epochs10训练轮数太多过拟合bert_pathmodel_hub/...权重路径路径错直接崩save_step500存 checkpoint 间隔太大丢进度learning_rate是血泪经验点BERT 微调千万别用 1e-3 这种大学习率loss 会直接飞掉2e-5 到 5e-5 是安全区间。max_seq_len要覆盖你数据里 95% 以上句子的长度否则实体被截断NER 标签和文本对不上训练时 loss 看着降但预测全是错的。3.2 启动训练与 checkpoint 管理确认权重放好后训练命令很直接# 先跑 NER产出实体识别模型 python ner_main.py --config ner_args.json # NER 跑完再跑 RE两者独立 python re_main.py --config re_args.json逻辑说明两个脚本独立运行不要试图并行因为它们会争抢同一块 GPU 显存。checkpoint目录下会按save_step存多个pytorch_model.bin文件名通常带 step 数。断点续训时把配置里的resume_path指向最近一个 checkpoint 即可但要注意优化器状态是否一起存了——如果只存了模型权重续训后学习率调度会从头开始前几个 step 的 loss 会抖一下属于正常现象。3.3 用 predict.py 验证三元组输出训练完不验证等于没跑。predict.py加载两个阶段的权重对输入句子输出三元组列表from predict import TripleExtractor # 初始化抽取器内部会分别加载 NER 和 RE 权重 extractor TripleExtractor( ner_model_pathcheckpoint/ner/best_model, re_model_pathcheckpoint/re/best_model, bert_pathmodel_hub/chinese-bert-wwm-ext ) text 张三出生于北京后来在上海工作。 triples extractor.extract(text) for h, r, t in triples: print(f({h}, {r}, {t})) # 期望输出类似(张三, 出生地, 北京)逻辑说明extract内部先调 NER 得到[(张三, 人物), (北京, 地点), (上海, 地点)]再配对跑 RE。参数上ner_model_path和re_model_path要指向实际存下来的目录不是单个.bin文件。如果输出为空先单独测 NER 看实体有没有识别出来再测 RE分段排查比整体调快得多。4. 避坑与排查这份源码最容易翻车的五个地方资源能跑通不代表你拿到就能跑通环境、路径、数据格式任何一个不对都会卡住。下面五条是我实际拆这类工程时最常遇到的按“现象 → 原因 → 解决”写清楚。4.1 报错 “Cant load config for model_hub/chinese-bert-wwm-ext”现象一运行就抛OSError提示找不到config.json。原因placeholder.txt占位权重目录是空的或者你下载的权重目录名和配置里写的不一致。解决去确认model_hub/chinese-bert-wwm-ext下确实有config.json、pytorch_model.bin、vocab.txt三个文件缺一不可目录名大小写敏感别改成chinese_bert_wwm_ext。4.2 训练 loss 一直是 nan现象前几个 step loss 正常突然变 nan 再也不降。原因九成是学习率太大或者数据里有空文本导致除零。解决先把learning_rate降到 2e-5加warmup_ratio0.1再检查process.py输出过滤掉len(text)0的样本。如果还 nan把max_grad_norm设成 1.0 做梯度裁剪。4.3 NER 标签和文本长度对不上现象DataLoader报维度不匹配或者collate_fn里 stack 失败。原因process.py里标签序列长度和input_ids长度不一致通常是 tokenizer 把某些字符拆成了 subword而标签还是按字符对齐的。解决要么用is_split_into_wordsTrue按字输入要么在转换时同步做 subword 对齐把B-/I-标签扩展到对应 subword 上-100用于忽略非首 subword 的 loss。4.4 预测结果里实体重叠或关系错配现象输出三元组里同一个实体被标了两种类型或者关系明显不对。原因NER 解码时没做约束BIO 序列出现B-后面跟O再跟I-的非法转移RE 阶段实体配对没做类型过滤。解决NER 解码加合法转移约束禁止O → I-RE 配对前按关系 schema 做类型白名单比如“出生地”只允许“人物-地点”组合。4.5 换自己的数据集后全部预测为“无关系”现象在自己标注的数据上跑RE 输出清一色no_relation。原因关系类别数和rel2id没同步更新或者新数据里正负样本极度不平衡。解决先确认re_args.json里的num_labels等于你实际关系类别数加一含无关系再统计训练集里各关系占比对稀有类别做重采样或调class_weight。5. 进阶技巧用类型约束把 RE 推理速度提上来跑通之后如果嫌慢瓶颈基本都在 RE 阶段的实体配对。一句话 20 个实体就是 380 对每对都要过一次 BERT推理时间直接爆炸。我一般会加一层类型约束先定义关系 schema明确每种关系允许的头实体类型和尾实体类型配对时只保留满足类型组合的对。比如“出生地”只允许(人物, 地点)那“人物-人物”“地点-地点”的组合全部跳过。实测在duie数据上能把 RE 推理量砍掉 60% 以上精度几乎不掉因为类型不符的配对本来就不该有关系。具体做法是在predict.py里加一个 schema 字典# 关系 schema每种关系允许的 (头实体类型, 尾实体类型) REL_SCHEMA { 出生地: (人物, 地点), 工作单位: (人物, 机构), 所属城市: (机构, 城市), # 按你的数据集补全 } def filter_pairs(entities, rel_type): 按关系类型过滤实体对减少无效 RE 推理 allowed REL_SCHEMA.get(rel_type, None) if allowed is None: return [(h, t) for h in entities for t in entities if h ! t] head_type, tail_type allowed heads [e for e in entities if e[1] head_type] tails [e for e in entities if e[1] tail_type] return [(h, t) for h in heads for t in tails]逻辑说明REL_SCHEMA是关系抽取里常说的“schema 约束”它把开放式的实体配对变成受控配对。参数上entities是 NER 输出的(文本, 类型)列表rel_type是当前要判断的关系。注意这个过滤是“按关系类型分别配对”也就是对每种关系都重新筛一遍实体而不是先配对再过滤这样能最大化减少无效计算。代价是如果 schema 定义不全漏掉的关系就永远抽不出来所以 schema 要覆盖你数据里所有关系类型。另一个验证技巧是拿一小批人工标注的测试句跑完预测后逐条对比重点看“漏抽”和“错抽”哪个多。漏抽多说明 NER 召回不够错抽多说明 RE 精度不够两者优化方向完全不同。我现在的习惯是每次改完 schema 或解码逻辑都强制拿这 20 条固定测试句跑一遍回归确认没有把之前对的结果改坏。希望这份拆解能帮你少走点弯路把这份毕设资源真正跑成自己的东西。本文还有配套的精品资源点击获取
返回列表