據(jù)庫(kù)Schema到本體建模:知識(shí)圖譜語(yǔ)義推理實(shí)戰(zhàn)指南)
1. 為什么數(shù)據(jù)庫(kù) Schema 不夠用了做了七八年后端和數(shù)倉(cāng)的朋友大概都有過(guò)這種體驗(yàn)業(yè)務(wù)表建了幾百?gòu)堊侄巫⑨寣?xiě)得密密麻麻ER 圖能鋪滿(mǎn)一整面墻可一旦要讓機(jī)器去“理解”這些數(shù)據(jù)之間的關(guān)系立刻就抓瞎了。數(shù)據(jù)庫(kù) Schema 是給程序員看的不是給機(jī)器推理用的。它能告訴你user_id是主鍵、order表有個(gè)外鍵指向它但它沒(méi)法告訴機(jī)器“一個(gè)訂單必然屬于某個(gè)用戶(hù)而用戶(hù)可能擁有多個(gè)收貨地址地址又關(guān)聯(lián)到具體城市”——這些語(yǔ)義層面的約束Schema 表達(dá)不了。這就是本體建模要解決的問(wèn)題。簡(jiǎn)單說(shuō)本體Ontology就是一套讓機(jī)器能讀懂的概念模型它用形式化的方式描述某個(gè)領(lǐng)域里有哪些概念、概念之間有什么關(guān)系、以及有哪些推理規(guī)則可以自動(dòng)推導(dǎo)出新知識(shí)。數(shù)據(jù)庫(kù) Schema 是“數(shù)據(jù)長(zhǎng)什么樣”本體是“知識(shí)長(zhǎng)什么樣”。前者管存儲(chǔ)后者管理解。我最初接觸本體建模是因?yàn)橐粋€(gè)知識(shí)圖譜項(xiàng)目。當(dāng)時(shí)團(tuán)隊(duì)想把公司內(nèi)部的商品、供應(yīng)商、合同、履約記錄串起來(lái)做智能問(wèn)答結(jié)果發(fā)現(xiàn)光靠數(shù)據(jù)庫(kù)外鍵根本撐不起“哪些供應(yīng)商同時(shí)給三個(gè)以上品類(lèi)供貨且合同金額超過(guò)某閾值”這類(lèi)查詢(xún)。后來(lái)引入 RDF 和 OWL 重新建模才把這件事跑通。這篇文章就把我從 Schema 遷移到本體的完整思路和踩過(guò)的坑整理出來(lái)適合有數(shù)據(jù)庫(kù)基礎(chǔ)、想入門(mén)知識(shí)表示與推理的開(kāi)發(fā)者。2. 從 Schema 到本體的思維轉(zhuǎn)換2.1 數(shù)據(jù)庫(kù) Schema 的本質(zhì)局限先厘清一件事數(shù)據(jù)庫(kù) Schema 不是不好而是它的設(shè)計(jì)目標(biāo)決定了它的邊界。關(guān)系型數(shù)據(jù)庫(kù)的 Schema 主要解決三件事——數(shù)據(jù)完整性約束、存儲(chǔ)結(jié)構(gòu)定義、查詢(xún)優(yōu)化。主鍵、外鍵、唯一索引、非空約束這些都是為了保證數(shù)據(jù)在寫(xiě)入時(shí)不出現(xiàn)邏輯矛盾。但它對(duì)“語(yǔ)義”的表達(dá)是隱式的藏在字段名和注釋里機(jī)器讀不懂。舉個(gè)例子。假設(shè)有一張employee表字段有id、name、dept_id、manager_id。人類(lèi)看到manager_id指向employee.id立刻明白這是上下級(jí)關(guān)系。但機(jī)器看到的只是一個(gè)整數(shù)外鍵它不知道這個(gè)外鍵代表“管理”關(guān)系更不知道“管理”關(guān)系具有傳遞性——如果 A 管理 BB 管理 C那么 A 間接管理 C。這種推理在 SQL 里要靠遞歸 CTE 手寫(xiě)而在本體里只要聲明manager是傳遞屬性推理機(jī)自動(dòng)就能推出來(lái)。再比如多表關(guān)聯(lián)的語(yǔ)義歧義。order表和user表通過(guò)user_id關(guān)聯(lián)這個(gè)關(guān)聯(lián)到底是“下單人”還是“收貨人”Schema 層面兩者長(zhǎng)得一模一樣但語(yǔ)義完全不同。本體建模時(shí)你會(huì)顯式定義placedBy和shippedTo兩個(gè)不同的對(duì)象屬性機(jī)器就能區(qū)分。2.2 本體的三元組世界觀本體的核心數(shù)據(jù)模型是三元組Triple主語(yǔ)-謂語(yǔ)-賓語(yǔ)。任何知識(shí)都可以拆成三元組。比如“張三是一家科技公司的員工”拆成(張三, 就職于, 某科技公司)。這種極簡(jiǎn)結(jié)構(gòu)的好處是機(jī)器解析零歧義而且天然支持圖結(jié)構(gòu)存儲(chǔ)和推理。RDFResource Description Framework就是三元組的標(biāo)準(zhǔn)表示格式。它用 URI 來(lái)標(biāo)識(shí)每一個(gè)資源確保全球唯一。比如http://example.org/employee/001這個(gè) URI 唯一標(biāo)識(shí)了某個(gè)員工。謂語(yǔ)也是 URI比如http://example.org/ontology/worksFor。這樣不同系統(tǒng)之間只要約定好 URI就能無(wú)縫合并數(shù)據(jù)。我剛開(kāi)始覺(jué)得 URI 又長(zhǎng)又啰嗦后來(lái)才體會(huì)到它的妙處全局唯一標(biāo)識(shí)是數(shù)據(jù)融合的前提。數(shù)據(jù)庫(kù)里兩張表都叫user合并時(shí)必然沖突但 RDF 里兩個(gè) URI 不同就是不同資源相同就是相同資源不存在歧義。2.3 RDFS 與 OWL 的分工RDFSRDF Schema和 OWLWeb Ontology Language是本體建模的兩層能力。RDFS 提供最基礎(chǔ)的詞匯rdfs:Class定義類(lèi)rdfs:subClassOf定義子類(lèi)關(guān)系rdfs:domain和rdfs:range約束屬性的定義域和值域。它相當(dāng)于本體的“骨架”能表達(dá)類(lèi)層次和屬性歸屬。OWL 在 RDFS 之上增加了更豐富的表達(dá)能力owl:ObjectProperty和owl:DatatypeProperty區(qū)分對(duì)象屬性和數(shù)據(jù)屬性owl:TransitiveProperty聲明傳遞屬性owl:inverseOf聲明逆屬性owl:disjointWith聲明互斥類(lèi)還有基數(shù)約束owl:cardinality、owl:minCardinality等。選型建議很直接如果只需要類(lèi)層次和簡(jiǎn)單屬性約束RDFS 夠用如果需要自動(dòng)推理、一致性檢查、復(fù)雜約束上 OWL。我一般直接用 OWL因?yàn)樗谋磉_(dá)力覆蓋 RDFS而且推理機(jī)如 HermiT、Pellet對(duì) OWL 的支持更完整。3. 本體建模的核心構(gòu)件與實(shí)操3.1 類(lèi)與類(lèi)層次的劃分原則類(lèi)Class是本體的基本單元對(duì)應(yīng)數(shù)據(jù)庫(kù)里的“實(shí)體類(lèi)型”。但本體的類(lèi)劃分比數(shù)據(jù)庫(kù)表設(shè)計(jì)更講究語(yǔ)義一致性。數(shù)據(jù)庫(kù)表可以為了性能冗余本體類(lèi)必須保持概念純凈。劃分類(lèi)的三條實(shí)操原則單一職責(zé)一個(gè)類(lèi)只表達(dá)一個(gè)概念。不要建UserAndOrder這種混合類(lèi)用戶(hù)和訂單是兩個(gè)獨(dú)立概念。層次清晰用subClassOf建立 IS-A 關(guān)系。比如Employee subClassOf PersonManager subClassOf Employee。層次不要太深一般三到四層足夠太深會(huì)導(dǎo)致推理效率下降?;コ饴暶魅绻麅蓚€(gè)類(lèi)不可能有交集用owl:disjointWith聲明。比如Individual和Organization互斥推理機(jī)就能發(fā)現(xiàn)“某個(gè)資源同時(shí)是個(gè)人和組織”這種邏輯矛盾。我踩過(guò)的一個(gè)坑早期把“部門(mén)”和“團(tuán)隊(duì)”建成兩個(gè)獨(dú)立類(lèi)結(jié)果發(fā)現(xiàn)團(tuán)隊(duì)是部門(mén)的子類(lèi)但有些團(tuán)隊(duì)跨部門(mén)。后來(lái)改成Team subClassOf OrganizationalUnitDepartment subClassOf OrganizationalUnit兩者互斥跨部門(mén)團(tuán)隊(duì)單獨(dú)建CrossFunctionalTeam類(lèi)邏輯就順了。3.2 對(duì)象屬性與數(shù)據(jù)屬性的區(qū)分屬性分兩類(lèi)這個(gè)區(qū)分是本體建模的關(guān)鍵對(duì)象屬性O(shè)bjectProperty連接兩個(gè)資源比如worksFor連接Person和Organization。數(shù)據(jù)屬性DatatypeProperty連接資源和字面量比如age連接Person和整數(shù)。區(qū)分標(biāo)準(zhǔn)很簡(jiǎn)單值是不是另一個(gè)資源。是資源就用對(duì)象屬性是具體值字符串、數(shù)字、日期就用數(shù)據(jù)屬性。實(shí)操中要注意屬性的 domain 和 range 聲明。worksFor的 domain 是Personrange 是Organization。推理機(jī)會(huì)根據(jù)這個(gè)約束做類(lèi)型推斷如果某個(gè)資源有worksFor屬性它自動(dòng)被推斷為Person。這個(gè)特性在數(shù)據(jù)校驗(yàn)時(shí)非常有用。還有一個(gè)容易忽略的點(diǎn)屬性也可以有層次。比如manages是supervises的子屬性那么所有manages關(guān)系自動(dòng)也是supervises關(guān)系。這種屬性層次在查詢(xún)時(shí)能自動(dòng)擴(kuò)展減少顯式聲明。3.3 用 OWL 表達(dá)復(fù)雜約束OWL 真正強(qiáng)大的地方在于復(fù)雜約束表達(dá)。舉幾個(gè)我實(shí)際用過(guò)的場(chǎng)景基數(shù)約束一個(gè)訂單必須至少有一個(gè)商品項(xiàng)用owl:minCardinality 1聲明。如果數(shù)據(jù)里出現(xiàn)沒(méi)有商品項(xiàng)的訂單推理機(jī)直接報(bào)不一致。值約束合同的狀態(tài)只能是“草稿、生效、終止”三個(gè)值之一用owl:oneOf枚舉聲明。這比數(shù)據(jù)庫(kù)的 CHECK 約束更靈活因?yàn)樗峭评韺用娴牟灰蕾?lài)具體數(shù)據(jù)庫(kù)實(shí)現(xiàn)。屬性鏈hasParent和hasSibling可以組合出hasUncle。OWL 2 支持屬性鏈owl:propertyChainAxiom能表達(dá)“父親的兄弟是叔叔”這種規(guī)則。逆屬性employs和worksFor互為逆屬性。聲明之后只要知道 A employs B推理機(jī)自動(dòng)推出 B worksFor A不需要雙向存儲(chǔ)。這些約束寫(xiě)進(jìn)本體后數(shù)據(jù)一致性檢查變成自動(dòng)化的。新數(shù)據(jù)入庫(kù)時(shí)推理機(jī)會(huì)檢查是否違反任何約束違反就拒絕或標(biāo)記。這比寫(xiě)一堆業(yè)務(wù)校驗(yàn)代碼可靠得多。3.4 從數(shù)據(jù)庫(kù) Schema 映射到本體的實(shí)操步驟具體怎么從現(xiàn)有數(shù)據(jù)庫(kù)遷移到本體我總結(jié)了一套五步法提取實(shí)體每張主表對(duì)應(yīng)一個(gè)類(lèi)。注意不是所有表都是實(shí)體關(guān)聯(lián)表多對(duì)多中間表通常映射為對(duì)象屬性而非類(lèi)。提取屬性表字段映射為數(shù)據(jù)屬性。外鍵映射為對(duì)象屬性。字段名要重命名為語(yǔ)義清晰的 URI比如dept_id改成belongsToDepartment。建立層次分析表之間的繼承關(guān)系如果有用subClassOf表達(dá)。沒(méi)有繼承關(guān)系的表找共同上位概念建父類(lèi)。聲明約束把數(shù)據(jù)庫(kù)的 NOT NULL、UNIQUE、CHECK 約束翻譯成 OWL 基數(shù)約束和值約束。外鍵約束翻譯成 domain/range。驗(yàn)證推理用推理機(jī)跑一遍檢查是否有不一致或意外推斷。這一步經(jīng)常能發(fā)現(xiàn)數(shù)據(jù)庫(kù)設(shè)計(jì)里隱藏的語(yǔ)義問(wèn)題。我做過(guò)一個(gè)供應(yīng)商管理系統(tǒng)的遷移原本 47 張表映射后得到 32 個(gè)類(lèi)、58 個(gè)對(duì)象屬性、41 個(gè)數(shù)據(jù)屬性。遷移過(guò)程中發(fā)現(xiàn)三處數(shù)據(jù)庫(kù)設(shè)計(jì)缺陷兩個(gè)本應(yīng)互斥的狀態(tài)字段沒(méi)有約束、一個(gè)多對(duì)多關(guān)系缺少中間屬性、一個(gè)字段的語(yǔ)義在不同模塊里不一致。這些問(wèn)題在 Schema 層面永遠(yuǎn)發(fā)現(xiàn)不了。4. 推理能力帶來(lái)的實(shí)際價(jià)值4.1 自動(dòng)分類(lèi)與類(lèi)型推斷本體最直接的價(jià)值是自動(dòng)分類(lèi)。聲明了類(lèi)和屬性的約束后推理機(jī)會(huì)自動(dòng)把資源歸入正確的類(lèi)。比如定義了VIPCustomer是“年消費(fèi)超過(guò) 10 萬(wàn)且注冊(cè)超過(guò)兩年的客戶(hù)”只要數(shù)據(jù)滿(mǎn)足條件推理機(jī)自動(dòng)把該客戶(hù)標(biāo)記為 VIP不需要人工打標(biāo)或定時(shí)任務(wù)。這個(gè)能力在業(yè)務(wù)規(guī)則頻繁變化的場(chǎng)景下特別香。規(guī)則變了改本體聲明就行不用改代碼、不用重跑數(shù)據(jù)。我做過(guò)一個(gè)風(fēng)控項(xiàng)目風(fēng)險(xiǎn)等級(jí)規(guī)則改了七版每次只改 OWL 聲明推理結(jié)果自動(dòng)更新開(kāi)發(fā)量幾乎為零。4.2 一致性檢查與數(shù)據(jù)質(zhì)量推理機(jī)的另一個(gè)核心能力是一致性檢查。本體里聲明的所有約束構(gòu)成一個(gè)邏輯系統(tǒng)任何違反約束的數(shù)據(jù)都會(huì)導(dǎo)致不一致。推理機(jī)能在毫秒級(jí)發(fā)現(xiàn)這些問(wèn)題。常見(jiàn)的不一致類(lèi)型包括類(lèi)型沖突同一個(gè)資源既是個(gè)體又是組織、基數(shù)違反訂單沒(méi)有商品項(xiàng)、值域違反年齡填了負(fù)數(shù)、互斥類(lèi)沖突一個(gè)資源同時(shí)屬于兩個(gè)互斥類(lèi)。我把這個(gè)能力用在數(shù)據(jù)入庫(kù)管道里數(shù)據(jù)進(jìn)庫(kù)前先過(guò)推理機(jī)不一致的直接攔截并記錄原因。上線三個(gè)月數(shù)據(jù)質(zhì)量問(wèn)題的發(fā)現(xiàn)時(shí)間從平均兩天縮短到實(shí)時(shí)。4.3 知識(shí)圖譜查詢(xún)的語(yǔ)義擴(kuò)展本體支撐的查詢(xún)和 SQL 查詢(xún)有本質(zhì)區(qū)別。SQL 查詢(xún)是精確匹配你寫(xiě)什么條件就查什么。本體查詢(xún)是語(yǔ)義擴(kuò)展查詢(xún)會(huì)自動(dòng)沿類(lèi)層次和屬性層次展開(kāi)。比如查詢(xún)“所有管理人員”如果Manager subClassOf Employee那么查詢(xún)Manager會(huì)自動(dòng)包含所有子類(lèi)實(shí)例。如果manages subPropertyOf supervises查詢(xún)supervises會(huì)自動(dòng)包含manages關(guān)系。這種擴(kuò)展在 SQL 里要靠手寫(xiě) UNION 或遞歸在本體里是自動(dòng)的。實(shí)際項(xiàng)目中這個(gè)特性讓查詢(xún)接口的復(fù)用率大幅提升。同一個(gè)查詢(xún)模板隨著本體演化自動(dòng)適配新類(lèi)和新屬性不用改查詢(xún)代碼。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 推理機(jī)不返回預(yù)期結(jié)果怎么辦這是新手最常遇到的問(wèn)題。排查順序建議如下排查項(xiàng)檢查方法常見(jiàn)原因本體是否加載成功打印本體三元組數(shù)量文件路徑錯(cuò)誤或格式錯(cuò)誤推理機(jī)是否啟用檢查推理配置默認(rèn)可能未開(kāi)啟推理約束是否聲明正確單獨(dú)測(cè)試每條約束domain/range 寫(xiě)反是否有不一致運(yùn)行一致性檢查數(shù)據(jù)違反約束導(dǎo)致推理中止命名空間是否匹配檢查 URI 前綴前綴不一致導(dǎo)致資源不匹配我遇到最多的是命名空間不匹配。本體文件里用了http://example.org/ont#數(shù)據(jù)里用了http://example.org/ontology#推理機(jī)認(rèn)為這是兩個(gè)不同的本體自然推不出結(jié)果。排查時(shí)先打印所有 URI肉眼比對(duì)前綴。5.2 本體太復(fù)雜導(dǎo)致推理變慢OWL 推理的計(jì)算復(fù)雜度跟本體的表達(dá)力直接相關(guān)。用了太多owl:unionOf、owl:complementOf、復(fù)雜基數(shù)約束推理時(shí)間會(huì)指數(shù)級(jí)上升。優(yōu)化策略能不用 OWL 2 DL 就不用很多場(chǎng)景 OWL 2 RL 或 EL 就夠推理復(fù)雜度低很多。減少匿名類(lèi)匿名類(lèi)restriction會(huì)顯著增加推理負(fù)擔(dān)能用命名類(lèi)就用命名類(lèi)。分層推理把穩(wěn)定不變的本體部分預(yù)推理并緩存只對(duì)增量數(shù)據(jù)做推理。限制基數(shù)約束范圍owl:minCardinality比owl:exactCardinality推理快能用 min/max 就不用 exact。我做過(guò)一個(gè)測(cè)試同一個(gè)本體用 OWL 2 DL 推理 10 萬(wàn)條數(shù)據(jù)要 47 秒換成 OWL 2 EL 只要 3 秒而業(yè)務(wù)需要的推理能力 EL 完全覆蓋。選對(duì) profile 比優(yōu)化代碼有效得多。5.3 數(shù)據(jù)庫(kù)遷移到本體的常見(jiàn)陷阱不要把表結(jié)構(gòu)直接翻譯成本體表是為了存儲(chǔ)優(yōu)化的本體是為了語(yǔ)義優(yōu)化的。中間表、冗余字段、歷史遺留字段不要映射。注意 NULL 的語(yǔ)義數(shù)據(jù)庫(kù)的 NULL 表示“未知”本體里沒(méi)有 NULL 概念。要么用owl:minCardinality 0表示可選要么用開(kāi)放世界假設(shè)處理。開(kāi)放世界假設(shè) vs 封閉世界假設(shè)數(shù)據(jù)庫(kù)是封閉世界沒(méi)查到就是不存在本體默認(rèn)是開(kāi)放世界沒(méi)聲明不代表不存在。這個(gè)差異會(huì)導(dǎo)致查詢(xún)結(jié)果不同需要顯式配置。不要過(guò)度建模本體不是越全越好。只建業(yè)務(wù)需要的類(lèi)和屬性用不到的不要建否則推理負(fù)擔(dān)和維護(hù)成本都會(huì)上升。5.4 工具選型速查工具用途適用場(chǎng)景Protégé本體編輯可視化建模、調(diào)試Apache JenaRDF 存儲(chǔ)與查詢(xún)Java 生態(tài)、SPARQL 查詢(xún)RDF4JRDF 存儲(chǔ)與查詢(xún)Java 生態(tài)、輕量級(jí)HermiTOWL 推理一致性檢查、分類(lèi)推理PelletOWL 推理復(fù)雜約束推理GraphDB圖數(shù)據(jù)庫(kù)生產(chǎn)環(huán)境、大規(guī)模數(shù)據(jù)Stardog圖數(shù)據(jù)庫(kù)企業(yè)級(jí)、帶推理我個(gè)人習(xí)慣用 Protégé 建模和調(diào)試Jena 做程序化處理HermiT 做推理驗(yàn)證。生產(chǎn)環(huán)境用 GraphDB它內(nèi)置推理支持省去單獨(dú)部署推理機(jī)的麻煩。6. 本體演化的維護(hù)經(jīng)驗(yàn)本體不是建完就完事的業(yè)務(wù)變化必然帶來(lái)本體演化。演化時(shí)最怕的是破壞性變更導(dǎo)致已有數(shù)據(jù)推理失敗。我的做法是給本體加版本號(hào)每次變更記錄變更類(lèi)型新增類(lèi)/屬性是安全變更修改 domain/range 是危險(xiǎn)變更刪除類(lèi)/屬性是破壞性變更。危險(xiǎn)和破壞性變更必須走數(shù)據(jù)遷移流程先備份、再遷移、后驗(yàn)證。還有一個(gè)實(shí)用技巧保留廢棄類(lèi)和屬性的聲明標(biāo)記為 deprecated不要直接刪除。這樣舊數(shù)據(jù)仍能通過(guò)推理只是不再產(chǎn)生新實(shí)例。等確認(rèn)所有舊數(shù)據(jù)都遷移完畢再?gòu)氐滓瞥?。本體建模這件事入門(mén)門(mén)檻不高但要做好需要持續(xù)迭代。我最大的體會(huì)是本體是活的不是死的。它應(yīng)該跟著業(yè)務(wù)一起生長(zhǎng)而不是建完就鎖進(jìn)柜子里。每次業(yè)務(wù)規(guī)則變化都是本體演化的機(jī)會(huì)也是檢驗(yàn)本體設(shè)計(jì)是否合理的時(shí)機(jī)。