资讯动态

从零构建农业知识图谱:本体建模、NER与关系抽取全流程实践

发布时间:2026/9/17 18:07:54 来源:尧图企业网站定制
简介面向智慧农业的知识图谱项目AgriKG源自上海农业农村大数据共享服务平台建设课题由华东师范大学数据科学与工程学院构建旨在基于碎片化农业大数据实现农业领域信息检索、命名实体识别、关系抽取、智能问答与辅助决策。压缩包内包含完整项目代码与数据共462个文件涵盖Python脚本114个用于实体识别、关系抽取及后端逻辑、JavaScript与前端页面88个JS、31个HTML、27个CSS呈现可视化界面、JSON/CSV数据文件17个JSON、16个CSV存放农业知识数据、文本与文档56个TXT、13个MD等包体约349.79MB。项目虽已停止维护代码仅供参考但数据可免费用于学术等非商业用途配套DASFAA 2019论文有助于理解整体设计思路。目前已有1452人学习下载适合NLP、知识图谱及智慧农业方向的研究者参考其工程实现与数据组织方式。1. AgriKG 要解决的信息检索难题不是搜不到而是搜不准同样是“水稻得了稻瘟病怎么办”地方植保站存的是 Word 表格期刊论文里是完整的发病规律和防治方案农户微信群里的说法可能是“稻叶上长灰色斑点”。把这些不同形态、不同术语的信息统一检索出来再给出一份可执行的用药建议传统关键词搜索做不到原因是“稻瘟病”“叶瘟”“稻热病”指向的是同一个实体。农业知识图谱AgriKG就是为这类场景落地的数据组织方式先用命名实体识别把非结构化文本里的领域名词对齐成统一实体再用关系抽取把“作物—病害—农药”之间的语义连接抽出来存成图结构最后通过智能问答和辅助决策把知识返回给使用者。适合正在做智慧农业、农业信息检索或者垂直领域知识图谱的工程师——本文只讨论你自己能从零搭起来的那一套实现路径。2. 农业知识图谱的本体建模与图存储先画关系再做抽取2.1 农业本体设计的最小闭环实体和关系不能拍脑袋定知识图谱构建有一个常见误区拿到一批农业语料就立刻跑命名实体识别指望模型把结构“吐”出来。实际工程上顺序是反的先把要支持的高频问句列出来反推实体类型和关系集合再去组织 NLP 流程。农业领域的实体并不复杂但需要明确边界。作物、品种、病害、虫害、天敌、农药、肥料、土壤、气象这类是实体品种具有抗性、农药具有剂量和安全间隔期这是属性。真正决定图谱检索价值的是一组关系最少只要六类就能覆盖绝大多数农业问句。实体类型典型属性在图谱中的角色Crop作物学名、别名、类别问题主体Disease病害病原、发病条件需要防治的对象Pest虫害拉丁名、为害部位需要防治的对象Pesticide农药剂量、安全间隔期防治手段AgronomicOp农事操作时间、要点栽培指导关系集合建议按“闭环”设计。比如作物和病害之间建立 HAS_DISEASE病害和农药之间建立 CONTROLLED_BY作物和品种之间建立 HAS_VARIETY。这样用户问“水稻得稻瘟病打什么药”图谱才能沿 Crop → Disease → Pesticide 这条路径把答案找出来。如果只有实体没有关系边图谱就退化成一张属性表辅助决策也无从谈起。实际项目中我一般会在白板上画一张闭环图作物感染病虫害病虫害被农药控制农事操作影响作物生长土壤与气象影响病虫害发生。这个循环里的每条边都对应至少一个用户高频问句没有用户问句支持的关系类型第一版先不建。2.2 用 Cypher 把本体落进 Neo4j命名规范与约束先行图谱存储选 Neo4j 是出于查询便捷性的考虑Cypher 对路径检索的表达能力强于 SQL 多层 JOIN。建库第一步不是灌数据而是立约束。下面这段 CQL 可以直接在建库阶段执行CREATE CONSTRAINT crop_name_unique IF NOT EXISTS FOR (c:Crop) REQUIRE c.name IS UNIQUE; CREATE CONSTRAINT pest_name_unique IF NOT EXISTS FOR (p:Pest) REQUIRE p.name IS UNIQUE; CREATE INDEX IF NOT EXISTS FOR (d:Disease) ON (d.name);约束的作用是保证后面 MERGE 不产生重复节点。注意这里唯一键选择的是 name 字段那么写入前必须先把“玉米”“苞米”“棒子”这类别名归一到同名否则同名异写会绕过约束产生两个节点。索引给 Disease 等高频实体类型单独建查询走点查时性能差一个数量级。接着写入第一批本体实例MERGE (c:Crop {name: 水稻}) SET c.latinName Oryza sativa, c.category 粮食作物 MERGE (p:Pest {name: 二化螟}) SET p.feedingPart 茎部 MERGE (c)-[:HAS_PEST {certainty: 0.97, source: 地方植保手册}]-(p);MERGE 会先按 name 查约束命中即返回现有节点未命中才创建。SET 用来补属性避免重复执行脚本时覆盖已有信息。关系上的 certainty 和 source 两个属性建议一定保留前者在多源数据冲突时做取舍依据后者保证辅助决策里每个答案都能回溯到原始文献或手册这是农业知识图谱和通用百科图谱的一大区别。2.3 批量导入三元组LOAD CSV 的顺序和避坑方式手工 MERGE 只适合验证阶段。真实语料抽出的三元组一般有几十万条统一走 CSV 导入。Neo4j 的 import 目录下放好边表文件后执行LOAD CSV WITH HEADERS FROM file:///agri_kg_edges.csv AS row MATCH (head:Crop {name: row.head}) MATCH (tail:Disease {name: row.tail}) MERGE (head)-[:HAS_DISEASE {source: row.source, certainty: toFloat(row.certainty)}]-(tail);这里有个顺序问题必须先用 MATCH 锁定头尾实体再 MERGE 关系。如果跳过 MATCH 直接用 MERGE 关系会在实体不存在时把缺失节点一并创建出来后续检查数据时很难排查。csv 里的一行如果匹配不到实体这一条边会被跳过所以边表入库前一定要先确认实体表完整。导入完成后用路径查询验证一下连通性MATCH path (:Crop {name: 水稻})-[:HAS_PEST]-(:Pest) RETURN path LIMIT 10;查结果时多用 LIMIT尤其是网页端 Neo4j Browser数据量大时全量 RETURN 很容易让浏览器卡死。这一步验证的不是“有没有数据”而是“关系方向对不对”。很多项目实体抽取做得不错但边方向建反了导致问答阶段查不到路径。3. 农业命名实体识别低成本词典召回与NER微调的组合方案3.1 农业实体为什么不能直接用通用NER通用命名实体识别在新闻语料上表现很好但放到农业领域会出现三个具体问题一是别名分散“玉米”“苞米”“棒子”代表同一作物通用模型不会把三者映射到统一实体二是嵌套实体多“水稻条纹叶枯病”可以拆成“水稻”和“条纹叶枯病”两个实体前者是作物名后者是病害名但文本中紧挨着出现BIOES 标签体系下容易互相干扰三是新品种、新农药不断冒出来语料里出现“龙粳31”“稻花香2号”这类不在预训练词表里的词分词器一拆就碎。所以在 AgriKG 项目里推荐顺序是先上词典召回拿到确定性的收益再用序列标注模型去覆盖未见词和口语化表达。词典不需要一开始做全把本体表里已有的实体名和常见别名输进去即可后边边抽边补。3.2 用 Aho-Corasick 做农业词典精准召回Python 里做词典匹配用 pyahocorasick它把词典构造成 AC 自动机匹配速度和处理词条数量关系不大。核心代码import pyahocorasick from typing import List, Tuple agri_dict [ (水稻, Crop), (玉米, Crop), (稻瘟病, Disease), (二化螟, Pest), (高粱条螟, Pest), ] automaton pyahocorasick.Automaton() for idx, (name, etype) in enumerate(agri_dict): automaton.add_word(name, (idx, etype)) automaton.make_automaton() def entity_recall(text: str) - List[Tuple[int, int, str, str]]: results [] for end_idx, (idx, etype) in automaton.iter(text): start end_idx - len(agri_dict[idx][0]) 1 results.append((start, end_idx 1, agri_dict[idx][0], etype)) return results讲一下两个关键点。make_automaton 之后不能再 add_word所以要扩充词典只能重新构建自动机词典量大时可以考虑把实体表放数据库启动时加载进内存再构建。iter 返回的 end_idx 是匹配词最后一个字符的索引所以 start 要回退一个词长这里很多人会错写成 end_idx - len 0导致后续把标签切到错误边界上。词典召回的结果还需要做一个最长匹配去重。例如句子“水稻条纹叶枯病”会同时命中“水稻”和“条纹叶枯病”这是期望结果但如果词典里有“稻瘟病”和“水稻稻瘟病”需要优先保留较长的那一个标准做法是把重叠区间按长度排序保留最长且后处理的实体标注。3.3 用 BERT-CRF 序列标注兜底未见词词典召回覆盖不了新出现的品种名和农户口语缩写这一层交给序列标注。把语料切分成字符级 token标注 BIOES 标签。例如水 B-Crop 稻 I-Crop 条 B-Disease 纹 I-Disease 叶 I-Disease 枯 I-Disease 病 E-Disease使用 BIOES 的原因在于它强制要求 B 和 E 成对出现对嵌套实体边界的约束比 BIO 强。模型加载和训练参数的经验值如下from transformers import AutoModelForTokenClassification, AutoTokenizer, TrainingArguments label_list [O, B-Crop, I-Crop, B-Disease, I-Disease, B-Pest, I-Pest] model AutoModelForTokenClassification.from_pretrained( bert-base-chinese, num_labelslen(label_list) ) tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) training_args TrainingArguments( output_dir./agri_ner_model, learning_rate2e-5, per_device_train_batch_size32, per_device_eval_batch_size64, num_train_epochs5, evaluation_strategyepoch, save_strategyepoch, )几个参数的调整逻辑学习率从 5e-5 降到 2e-5是防止小语料上微调 BERT 时标签层波动过大农业标注数据通常只有几千句不属于大数据场景max_length 建议固定 128不要贪长——农业论文句子动辄上百字直接截断会切断实体边界需要在预处理阶段按句号、分号切句再进模型epoch 数设为 5 但配合早停看验证集 F1 不升就停训练 10 个以上 epoch 模型会过拟合到语料里的文献格式对口语问句的泛化不升反降。3.4 实体归一化识别之后必须做别名映射模型识别出来的“苞米”“玉米棒子”都要归一到 canonical name 才能匹配图里的唯一约束。用一张别名表做映射ENTITY_ALIAS { 苞米: 玉米, 玉米棒子: 玉米, 稻热病: 稻瘟病, } def normalize(name: str) - str: return ENTITY_ALIAS.get(name, name)归一化发生在实体识别和关系抽取之间。不在这一步做后边关系抽取抽出的三元组会出现“苞米—HAS_DISEASE—大斑病”和“玉米—HAS_DISEASE—大斑病”两条记录图谱里形成两个节点查询时按“玉米”永远搜不到“苞米”的数据。别名表从哪来第一版用人工整理跑起来之后每天从搜索日志里挖掘未命中的实体名按编辑距离自动生成候选别名人工确认后再追加。4. 农业关系抽取从触发词模板到远程监督回灌4.1 关系集合是有限且可控的关系抽取不是开放式的“抽取所有可能关系”而是在第 2 章定义的有限关系集合里做分类。对 AgriKG 第一版来说关系类型控制在 6 到 8 个就够了关系头实体尾实体示例三元组HAS_DISEASE作物病害(水稻, HAS_DISEASE, 稻瘟病)HAS_PEST作物虫害(水稻, HAS_PEST, 二化螟)CONTROLLED_BY病害/虫害农药(稻瘟病, CONTROLLED_BY, 三环唑)HAS_VARIETY品种作物(龙粳31, HAS_VARIETY, 水稻)PREFERRED_SOIL作物土壤(水稻, PREFERRED_SOIL, 酸性土)AFFECTED_BY_METEO病虫害气象条件(稻瘟病, AFFECTED_BY_METEO, 阴雨高湿)关系越少远程监督的样本质量越好控制。每定义一个关系先确认它对应的问句确实存在否则这张表会越滚越大最后变成标注噩梦。4.2 触发词加正则模板——最少代码跑通关系抽取对农业技术文献这种句式相对固定的文本触发词模板是投入产出比最高的方案。下面的代码直接复用第 3 章的实体识别结果import re PATTERNS [ (r(?Pcrop.{2,20})(?:感染|发生|患上)(?Pdisease.{2,20}), HAS_DISEASE), (r(?Pdisease.{2,20})(?:可(?:用|以)|使用|喷施)(?Ppesticide.{2,20})(?:防治|治疗), CONTROLLED_BY), ] def extract_triples(text: str, entities: list) - list: triples [] for pattern, rel_type in PATTERNS: for m in pattern.finditer(text): head m.groupdict().get(crop) or m.groupdict().get(disease) tail m.groupdict().get(disease) or m.groupdict().get(pesticide) if head in entities and tail in entities: triples.append((head, rel_type, tail)) return triples触发词是关系抽取里最重要的参数。农业文献写到防治方案时习惯句式是“XX 可用 XX 防治”所以 CONTROLLED_BY 的模板里把“可用”“使用”“喷施”作为触发词并且把“防治”“治疗”放在农药后面做闭合标记。这个模板能覆盖标准文献里七成左右的防治关系剩下的分散在长难句、被动句里交给下一节。正则模板的局限要认清它只能处理同一短句内的实体对跨句关联如“水稻在本田期易感病。稻瘟病在多雨年份流行”就抽不出来。解决路径是加一层依存句法分析把“易感”的 nsubj 和 obj 对应到实体但维护成本高建议先做远程监督。4.3 远程监督用种子三元组自动生成训练语料远程监督的思路是跨句级的已知图谱里有三元组 (稻瘟病, CONTROLLED_BY, 三环唑)现在拿这条三元组去匹配语料库里所有同时包含“稻瘟病”和“三环唑”的句子把这些句子全部标记为 CONTROLLED_BY 的正样本送入关系分类模型训练。实现步骤分为三步。第一步构建种子三元组文件可以从植保手册、农药登记数据库里的结构化数据直接转换。第二步对每个句子做实体召回打上 BIOES 标签然后按实体对检索匹配句子。第三步训练一个句子级别的关系分类器输入是“头实体 [SEP] 尾实体 [SEP] 整句文本”输出是关系类别或 none。远程监督有一个要处理的经典问题一句包含“水稻”和“稻瘟病”的文本可能只是在讲栽培历史并没有直接表达“感染”关系却被正样本误标。常见处理是降低置信度——把远程监督自动标注的数据称为候选正样本交由规则或人工抽检置信度低于阈值的句子不进训练集。使用大语言模型做候选样本预筛选也有效但筛完仍需保留可解释的触发词证据否则样本质量难以审计。4.4 三元组冲突消解同一实体对多条关系的取舍从多个来源抽取三元组时同一实体对可能出现多条相同类型关系有的置信度 0.95有的只有 0.6。需要做一次去重合并MATCH (a:Crop {name: 水稻})-[r:HAS_DISEASE]-(b:Disease) WITH a, b, collect(r) AS rels WHERE size(rels) 1 UNWIND rels AS r WITH a, b, rels, r ORDER BY r.certainty DESC WITH a, b, collect(r)[1..] AS to_delete UNWIND to_delete AS r DELETE r这段 Cypher 的执行顺序是先找出水稻所有指向同一病害且类型相同的边按 certainty 降序排列切掉第一条最可信的把剩余低置信度关系删除。注意 DELETE 前先把 to_delete 收集完整再执行不要在 UNWIND 过程中直接删否则列表遍历时元素变化会漏删。生产环境更稳妥的做法是不物理删除而是给每条边增加 status 字段标记 active/inactive查询时过滤这样误删还能找回。5. 农业智能问答与辅助决策意图模板加参数化Cypher的实现5.1 问句意图与查询模板映射知识图谱问答层的核心不是大模型而是把用户问句稳定映射到图查询。先把意图类型收敛成十来类例如病害防治、虫害防治、品种推荐、栽培要点、农资用法。用户输入“水稻出穗期遇到阴雨还得了稻瘟病该咋办”先抽作物实体“水稻”、病害实体“稻瘟病”意图分类判定为控制方案查询再映射到预定义查询模板。5.2 参数化 Cypher 模板避免字符串拼查询意图模板确定后用 Neo4j Python Driver 传参数执行MATCH (c:Crop {name: $cropName})-[:HAS_DISEASE]-(d:Disease {name: $diseaseName}) MATCH (d)-[:CONTROLLED_BY]-(p:Pesticide) RETURN p.name AS pesticide, p.dosage AS dosage, p.safetyInterval AS safetyInterval参数化查询有两个好处一是防止用户输入文本拼进 Cypher 造成注入二是利用 Neo4j 的查询计划缓存。cropName 和 diseaseName 必须传归一化后的名称否则用户说“苞米”而库里只有“玉米”查询直接返回空。问答接口层做一层实体链接把前端识别出的实体别名映射到 canonical name 再传入模板。5.3 用负样本路径做回归验证一个容易被忽略的验收手段是构造负路径测试故意查询不存在的疾病或错配的作物—病害组合验证图谱是否返回了不该返回的结果。例如玉米不会感染稻瘟病那么查询“玉米—稻瘟病”的关系路径必须返回空。把这些恒空查询写进 CI 脚本每次图谱数据更新后自动跑一遍。负路径测试能检测出实体归一化错误造成的误关联也能暴露远程监督样本把实体的错误关系灌进图里这两类问题在农业知识图谱里比抽取缺失更致命——因为用户搜不到会换词再搜搜到错的对决策的破坏无法估计。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价