ARTICLE DETAIL

资讯详情

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

知识图谱里的数据错了怎么办?从 pySHACL 讲透 RDF 数据校验与 SHACL 规则

知识图谱里的数据错了怎么办?从 pySHACL 讲透 RDF 数据校验与 SHACL 规则 很多人第一次接触知识图谱时会把注意力全部放在RDF三元组OntologyOWLSPARQLGraphRAG但真正把知识图谱放到生产环境里之后你很快会遇到另外一个非常现实的问题知识图谱里的数据写错了怎么办例如我们有一个人员知识图谱张三 - 姓名 - 张三 张三 - 年龄 - 28 张三 - 邮箱 - zhangsanexample.com 张三 - 所属公司 - 阿里巴巴看起来非常正常。但如果某一天数据变成这样张三 - 年龄 - 二十八 张三 - 邮箱 - 123456 张三 - 所属公司 - Alibaba从 RDF 的角度来说这些数据完全有可能依然是合法 RDF。问题在于RDF 语法合法不代表你的业务数据是正确的。这时候就轮到SHACL出场了。而 Python 生态里一个非常常用的 SHACL 校验工具就是pySHACL项目地址https://github.com/RDFLib/pySHACLpySHACL 是一个基于 Python 的 SHACL Validator可以使用 SHACL Shapes 对 RDF Graph 进行数据验证。项目底层使用 RDFLib 处理 RDF 数据并按照 SHACL 规范执行约束验证。今天我们不讲特别复杂的知识图谱理论。就讲清楚一个问题如果知识图谱里的数据错了怎么通过 pySHACL 自动找出来我们重点看 5 类非常实用的规则minCount datatype class pattern cardinality学完这几个已经足够解决相当一部分实际的数据质量问题。一、先搞清楚SHACL 到底解决什么问题先来看传统关系型数据库。假设我们有这样一张 MySQL 表CREATETABLEuser(idBIGINTPRIMARYKEY,nameVARCHAR(100)NOTNULL,ageINT,emailVARCHAR(255),company_idBIGINT);数据库天然可以帮我们限制一些东西。比如name 不能为空 age 必须是 INT id 必须唯一甚至可以进一步添加ALTERTABLEuserADDCONSTRAINTchk_ageCHECK(age0);也就是说关系型数据库不仅存数据还可以通过 Schema 和 Constraint 对数据进行约束。但是 RDF 世界不太一样。例如prefix ex: http://example.org/ . ex:zhangsan ex:name 张三 ; ex:age 二十八岁 ; ex:email 哈哈哈 .它是不是 RDF是。语法上并没有什么大问题。但从我们的业务角度age 二十八岁可能就是错误数据。因为我们希望年龄必须是xsd:integer邮箱也不能随便写。于是我们需要另外定义一套这个 RDF Graph 应该长什么样。这就是 SHACL 的核心思想。SHACL 全称Shapes Constraint Language你可以把它简单理解成给 RDF / 知识图谱定义数据校验规则。W3C 的 SHACL Recommendation 定义了包括sh:minCount、sh:maxCount、sh:datatype、sh:class、sh:pattern等约束组件用来描述 RDF 节点和属性应该满足什么条件。二、理解 SHACL 最简单的方法把它当成“知识图谱版 JSON Schema”很多程序员第一次看到 SHACLex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; ] .第一反应可能是这什么东西实际上思想特别简单。如果你用过 JSON Schema{type:object,required:[name],properties:{name:{type:string}}}SHACL 做的是类似的事情。只不过JSON Schema验证JSON而SHACL验证RDF Graph你甚至可以粗略这样理解RDF ≈ 数据 SHACL ≈ 数据规则 pySHACL ≈ 校验器整个流程就是RDF Data ↓ SHACL Shapes ↓ pySHACL ↓ Validation Report三、准备一个故意写错的知识图谱我们直接做一个人员知识图谱。新建data.ttl内容prefix ex: http://example.org/ . prefix xsd: http://www.w3.org/2001/XMLSchema# . ex:company001 a ex:Company ; ex:name JavaPub Technology . ex:zhangsan a ex:Person ; ex:name 张三 ; ex:age 28 ; ex:email zhangsanexample.com ; ex:worksFor ex:company001 .这里的数据目前没有什么问题。我们可以把它理解成张三 ├── 类型Person ├── 姓名张三 ├── 年龄28 ├── 邮箱zhangsanexample.com └── 公司company001接下来我们专门制定一套规则来检查它。四、第一个规则minCount检查“必填字段”这是 SHACL 里最好理解同时也是最常用的规则之一。假设我们的业务要求每一个 Person 都必须有 name。那么可以写prefix ex: http://example.org/ . prefix sh: http://www.w3.org/ns/shacl# . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; ] .重点就是sh:minCount 1意思是ex:name至少出现一次。W3C SHACL 规范中sh:minCount表示某个属性对应的 value nodes 最少应该有多少个当实际数量小于这个值时就会产生 Validation Result。把它翻译成程序员熟悉的写法大概就是iflen(person.name)1:raiseValidationError(name不能为空)或者 SQLnameVARCHAR(100)NOTNULL五、故意制造一条错误数据现在把ex:zhangsan a ex:Person ; ex:name 张三 .改成ex:zhangsan a ex:Person ; ex:age 28 .也就是说name 没了按照我们的 SHACL Shapesh:minCount 1这条数据应该无法通过。六、安装 pySHACLPython 环境直接安装pipinstallpyshacl官方项目也提供 CLI可以直接给它数据图和 Shapes Graph 进行校验。例如pyshacl-sshapes.ttl data.ttl其中data.ttl是真正的数据而shapes.ttl是数据规则可以进一步输出人类更容易阅读的报告pyshacl\-sshapes.ttl\-fhuman\data.ttl当 Graph 不满足 Shapes 时pySHACL 会返回 Non-Conformant 的校验报告。七、用 Python 调用 pySHACL很多时候我们不会单独跑命令而是集成到自己的 Python 服务里。例如frompyshaclimportvalidate conforms,results_graph,results_textvalidate(data_graphdata.ttl,shacl_graphshapes.ttl)print(校验是否通过,conforms)print(results_text)pySHACL 的validate()会返回三个主要结果conforms results_graph results_text分别可以理解为conforms ↓ 是否通过 results_graph ↓ RDF 格式的验证结果 results_text ↓ 人类可读的验证报告这也是 pySHACL 官方 Python API 的基本使用方式。因此我们可以这样写frompyshaclimportvalidate conforms,result_graph,result_textvalidate(data_graphdata.ttl,shacl_graphshapes.ttl,inferencenone,abort_on_firstFalse)ifconforms:print(数据正常)else:print(发现错误)print(result_text)这已经可以变成真正的数据质量检查程序了。八、datatype年龄不能写成“二十八岁”下面看第二个非常有价值的约束datatype假设我们规定Person.age必须是xsd:integerSHACL 可以这样写prefix ex: http://example.org/ . prefix sh: http://www.w3.org/ns/shacl# . prefix xsd: http://www.w3.org/2001/XMLSchema# . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; ] .正确数据ex:zhangsan ex:age 28 .或者显式写ex:zhangsan ex:age 28^^xsd:integer .但下面这种ex:zhangsan ex:age 二十八岁 .就不符合我们的约束。因为二十八岁本质是字符串。而我们要求xsd:integer这就是sh:datatype xsd:integer真正有价值的地方。九、datatype 非常适合抓 ETL 数据错误想象你从 Excel 导入知识图谱。原始表格姓名 年龄 张三 28 李四 35 王五 未知 赵六 31岁程序批量转换成 RDF 后可能得到ex:zhangsan ex:age 28 . ex:lisi ex:age 35 . ex:wangwu ex:age 未知 . ex:zhaoliu ex:age 31岁 .如果有几十万条数据你显然不可能人工一个个检查。这时候sh:datatype xsd:integer实际上就相当于一台自动知识图谱质检机。所有不是整数的数据都可以在入库阶段被拦下来。十、pattern邮箱格式对不对但是仅仅判断email 是 string显然还不够。因为下面这些都是字符串zhangsanexample.com 123456 abc 哈哈哈所以还需要pattern例如sh:property [ sh:path ex:email ; sh:datatype xsd:string ; sh:pattern ^[A-Za-z0-9._%-][A-Za-z0-9.-]\\.[A-Za-z]{2,}$ ; ] .这里sh:pattern就是正则表达式约束。SHACL 对sh:pattern的定义是使用兼容 SPARQLREGEX的模式对 value node 的字符串表示执行匹配。例如zhangsanexample.com可以通过。但是123456无法通过。十一、pattern 不只是检查邮箱很多人看到 pattern 第一反应就是正则。但在知识图谱场景里它非常有用。例如检查手机号sh:pattern ^1[3-9][0-9]{9}$ ;检查员工号EMP-2026-00001可以写sh:pattern ^EMP-[0-9]{4}-[0-9]{5}$ ;检查商品编号SKU-A001 SKU-B102可以写sh:pattern ^SKU-[A-Z][0-9]{3}$ ;甚至可以约束 URLsh:pattern ^https://.* ;所以pattern特别适合解决数据类型没问题但是内容格式错了。十二、class属性指向的对象类型对不对下面这个非常重要。假设Person有一个worksFor属性。我们的要求是worksFor 必须指向一个 Company。比如ex:zhangsan ex:worksFor ex:company001 . ex:company001 a ex:Company .这是合理的。SHACLsh:property [ sh:path ex:worksFor ; sh:class ex:Company ; ] .什么意思就是zhangsan | | worksFor ↓ company001 | | rdf:type ↓ Company十三、如果 worksFor 指向一个 Person 呢假设有人把数据写成ex:zhangsan ex:worksFor ex:lisi . ex:lisi a ex:Person .语法有没有问题没有。它依然是一组三元组张三 - 就职于 - 李四RDF 不会因为业务语义奇怪就自动拒绝。但是sh:class ex:Company就可以把它检查出来。这也是 SHACL 和普通 JSON Schema 一个非常大的不同。因为知识图谱里的数据关系往往是Node → Node所以不仅要检查属性值是什么类型还要检查属性指向的节点是什么 Class。十四、class 在实际知识图谱里特别重要比如医疗知识图谱Patient ↓ takesDrug Drug你可以约束sh:class ex:Drug公司知识图谱Employee ↓ worksFor Company可以约束sh:class ex:Company商品知识图谱Product ↓ belongsToCategory Category可以要求sh:class ex:Category科研知识图谱Paper ↓ author Researcher可以要求sh:class ex:Researcher这已经不仅仅是字段校验了。而是在验证知识图谱中的关系语义是否正确。十五、Cardinality一个人到底能有几个出生日期接下来讲一个知识图谱里非常重要的概念Cardinality中文一般叫基数简单理解就是一个属性允许出现多少次。SHACL 里非常常见的就是sh:minCount sh:maxCount例如sh:minCount 1 ; sh:maxCount 1 ;意思就是必须有而且只能有一个。SHACL 规范把sh:minCount和sh:maxCount都归到 Cardinality Constraint Components。minCount规定最少值数量maxCount规定最多值数量。十六、一个很典型的错误两个出生日期例如ex:zhangsan ex:birthDate 1995-01-01^^xsd:date ; ex:birthDate 1996-01-01^^xsd:date .语法完全合法。但是一般业务规则可能要求出生日期只能有一个那么sh:property [ sh:path ex:birthDate ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:date ; ] .这样0 个 birthDate不行。1 个 birthDate通过。2 个 birthDate也不行。是不是特别像数据库实际上就类似NOT NULL 单值约束十七、minCount 和 maxCount 可以组合出很多规则可选字段sh:maxCount 1表示0 或 1 个例如middleName有的人有有的人没有。必填字段sh:minCount 1表示至少一个必须且唯一sh:minCount 1 ; sh:maxCount 1 ;表示有且只有一个例如身份证号 出生日期 员工编号至少两个比如团队要求至少两位负责人sh:minCount 2最多三个标签sh:maxCount 3所以 SHACL 其实可以表达非常多业务规则。十八、把规则组合起来真正生产环境里不会只写一个约束。我们可以完整定义一个PersonShape例如prefix ex: http://example.org/ . prefix sh: http://www.w3.org/ns/shacl# . prefix xsd: http://www.w3.org/2001/XMLSchema# . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; ] ; sh:property [ sh:path ex:age ; sh:maxCount 1 ; sh:datatype xsd:integer ; ] ; sh:property [ sh:path ex:email ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; sh:pattern ^[A-Za-z0-9._%-][A-Za-z0-9.-]\\.[A-Za-z]{2,}$ ; ] ; sh:property [ sh:path ex:worksFor ; sh:minCount 1 ; sh:maxCount 1 ; sh:class ex:Company ; ] .翻译成人话Person name 必须存在 只能有一个 必须是字符串 age 最多一个 必须是整数 email 必须存在 只能有一个 必须是字符串 而且必须符合邮箱格式 worksFor 必须存在 只能有一个 而且必须指向 Company到这里知识图谱已经第一次拥有了真正意义上的数据契约。十九、现在故意造一份“全是 Bug”的 RDF例如prefix ex: http://example.org/ . ex:zhangsan a ex:Person ; ex:name 张三 ; ex:name 老张 ; ex:age 28岁 ; ex:email 123456 ; ex:worksFor ex:lisi . ex:lisi a ex:Person .这里至少有四个问题。第一name 出现了两个违反sh:maxCount 1第二age 28岁违反sh:datatype xsd:integer第三email 123456违反sh:pattern第四worksFor → lisi但lisi Person而我们要求worksFor → Company违反sh:class ex:Company这就是 SHACL 真正有意思的地方它不是检查 RDF 能不能解析而是在检查这些 RDF 数据是否符合我们定义的业务语义。二十、Python 里自动处理校验失败我们可以进一步写一个实际一点的程序frompyshaclimportvalidatedefvalidate_graph(data_file,shapes_file):conforms,results_graph,results_textvalidate(data_graphdata_file,shacl_graphshapes_file,inferencenone,abort_on_firstFalse,meta_shaclFalse,advancedFalse,debugFalse)ifconforms:print(✅ RDF 数据校验通过)returnTrueprint(❌ RDF 数据存在问题)print(-*80)print(results_text)returnFalseif__name____main__:validate_graph(data.ttl,shapes.ttl)这里特别注意abort_on_firstFalse意味着不要发现第一个错误就停。比如一份数据有27 个问题我们希望一次全部找出来而不是改一个 运行一次 再改一个 再运行一次二十一、还可以直接使用 RDFLib Graph如果你的知识图谱本来就是程序动态生成的就没有必要先保存文件。可以直接使用fromrdflibimportGraphfrompyshaclimportvalidate data_graphGraph()data_graph.parse(data prefix ex: http://example.org/ . ex:zhangsan a ex:Person ; ex:name 张三 ; ex:age 二十八 . ,formatturtle)shape_graphGraph()shape_graph.parse(data prefix ex: http://example.org/ . prefix sh: http://www.w3.org/ns/shacl# . prefix xsd: http://www.w3.org/2001/XMLSchema# . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; ] ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; ] . ,formatturtle)conforms,results_graph,results_textvalidate(data_graph,shacl_graphshape_graph)print(conforms)print(results_text)这样就能很容易接进Flask FastAPI Django ETL Pipeline Airflow 数据同步服务 知识图谱导入程序里面。二十二、SHACL 最适合放在哪一层我比较推荐一个思路Excel CSV MySQL API 爬虫 LLM ↓ 数据转换 ↓ RDF ↓ SHACL Validation ↓ 合法 ↓ Graph Database也就是说SHACL 放在知识图谱入库之前。例如rdftransform_to_rdf(raw_data)resultvalidate_rdf(rdf)ifresult:save_to_graph_database(rdf)else:save_to_error_queue(rdf)这样 Graph Database 里面尽量只留下干净数据。二十三、尤其是现在 LLM 生成知识图谱更需要 SHACL这几年非常流行一种做法文章 PDF 网页 合同 论文 ↓ LLM ↓ 实体抽取 关系抽取 ↓ RDF ↓ 知识图谱问题来了。大模型很强。但大模型不是数据库 Constraint。例如你告诉模型请从文章中提取人物姓名、年龄、邮箱和所属公司。它最终可能生成{name:张三,age:大约30岁,email:暂无,company:可能是某科技公司}人看起来似乎还能理解。但是知识图谱真正入库时age 大约30岁到底是不是整数company到底有没有对应 Company 节点email 暂无到底算邮箱还是缺失所以一个越来越实用的架构就是LLM ↓ 生成 RDF ↓ SHACL ↓ 发现错误 ↓ 修复 / 重试 ↓ Graph Database换句话说LLM 负责生成SHACL 负责守门。二十四、甚至可以让 LLM 根据 SHACL 自动修数据再进一步。假设 pySHACL 返回Person: zhangsan Property: age Constraint: datatype xsd:integer Current Value: 28岁我们完全可以把这个结果重新交给模型下面是一条 RDF 数据 ex:zhangsan ex:age 28岁 . SHACL 校验要求 ex:age 的 datatype 必须是 xsd:integer。 请修复 RDF。模型返回ex:zhangsan ex:age 28 .然后再次 pySHACL 校验如果通过写入数据库于是一个自动循环就出现了LLM Generate ↓ pySHACL Validate ↓ 失败 ↓ LLM Repair ↓ pySHACL Validate ↓ 成功 ↓ Graph DB这个设计其实非常适合现在的GraphRAG 企业知识库 AI Agent Memory Ontology Pipeline二十五、SHACL 和 OWL 到底有什么区别这里也是新手非常容易混淆的地方。有人会问有 OWL 了为什么还要 SHACL可以先用一个非常粗略、但容易理解的方法区分OWL 更关心 这个世界的知识意味着什么 SHACL 更关心 这份数据合不合格例如Employee 是 Person 的子类这比较像OWL / Ontology而Employee 必须有 employeeId employeeId 只能有一个 employeeId 必须符合 EMP-XXXXX 格式这明显更像SHACL所以实际系统中两者完全可以一起出现Ontology / OWL ↓ 定义知识语义 SHACL ↓ 定义数据质量约束而 pySHACL 本身也支持配合 RDFS / OWL-RL 推理进行验证例如其validate()API 可以通过inference参数配置none、rdfs、owlrl或both。二十六、最值得先掌握的 5 个 SHACL 规则如果你刚学 SHACL我并不建议一开始把所有 Constraint 都背下来。先记住下面几个就够了。sh:minCount解决这个字段是不是必须存在sh:maxCount解决最多允许几个值sh:datatype解决这个 Literal 是不是正确的数据类型sh:class解决这个关系指向的是不是正确类型的节点sh:pattern解决字符串内容是不是符合指定格式如果你能熟练使用这五个已经能覆盖很多常见的数据质量问题。二十七、最后看一张完整关系图整个知识图谱校验流程其实可以压缩成PersonShape │ ┌────────────┼────────────┐ │ │ │ name age email │ │ │ minCount1 datatype pattern maxCount1 integer EMAIL │ │ │ └────────────┼────────────┘ │ Person │ worksFor │ ▼ Company │ class check而 pySHACL 做的事情就是RDF Graph │ ▼ ┌────────────┐ │ pySHACL │ └────────────┘ ▲ │ SHACL Shapes │ ▼ Validation Report写在最后很多人学习知识图谱时只关注怎么把数据存进去但真正进入生产环境后更重要的问题往往变成怎么保证存进去的数据是对的因为错误的知识图谱比没有知识图谱更加危险。如果年龄错了可能只是一个字段错误。但如果人物关系错了 公司关系错了 药物关系错了 供应链关系错了这些错误最终进入GraphRAG 搜索系统 推荐系统 AI Agent 智能问答那么模型得到的上下文本身就是错误的。再强的大模型Garbage In ↓ Garbage Out所以在一个相对完整的知识图谱工程里我更喜欢这样的流程原始数据 ↓ RDF ↓ Ontology ↓ SHACL ↓ pySHACL ↓ 验证报告 ↓ 清洗 / 修复 ↓ Graph Database ↓ GraphRAG / Agent如果把 Ontology 看成知识图谱的语义说明书。那么 SHACL 更像知识图谱的质检标准。而 pySHACL就是那个真正拿着质检标准一条条帮你检查 RDF 数据的人。对于准备做知识图谱、GraphRAG、企业知识库的人来说这一层非常容易被忽略。但它可能恰恰决定了你的知识图谱到底只是“有数据”还是“数据真的可信”。
返回列表