戰(zhàn):從Protégé建模到Neo4j知識(shí)圖譜落地)
1. 為什么我勸你先搞懂本體工程再動(dòng)手建圖知識(shí)圖譜這個(gè)詞這幾年被說得太多了多到很多人一上來就問“用Neo4j怎么建圖”卻很少有人先問自己一句我到底要建一個(gè)什么樣的圖我見過太多團(tuán)隊(duì)數(shù)據(jù)灌進(jìn)去了節(jié)點(diǎn)和關(guān)系也連上了結(jié)果查詢的時(shí)候發(fā)現(xiàn)關(guān)系類型混亂、實(shí)體粒度不統(tǒng)一、同一個(gè)人在三張表里有三種寫法最后整個(gè)圖譜變成一團(tuán)理不清的毛線球。問題的根子不在圖數(shù)據(jù)庫而在建模之前的那一步——本體工程。打個(gè)比方建知識(shí)圖譜就像蓋一棟樓。圖數(shù)據(jù)庫是施工隊(duì)和建材RDF/屬性圖是磚塊的形狀而本體是建筑圖紙。你可以沒有圖紙就讓施工隊(duì)直接砌墻砌出來的東西也能住人但一旦想加個(gè)房間、改個(gè)承重結(jié)構(gòu)整棟樓就得推倒重來。本體工程干的就是畫圖紙這件事定義這個(gè)領(lǐng)域里有哪些概念類、這些概念有什么屬性數(shù)據(jù)屬性、概念之間能建立什么關(guān)系對(duì)象屬性、以及有哪些不能違反的規(guī)則公理和約束。這篇內(nèi)容適合三類人看。第一類是完全沒接觸過知識(shí)圖譜、想從零搭一個(gè)的開發(fā)者我會(huì)把每一步的操作和背后的理由都講清楚。第二類是已經(jīng)用過Neo4j或者圖數(shù)據(jù)庫、但建??偸欠倒さ墓こ處熌隳茉谶@里找到返工的根因。第三類是做數(shù)據(jù)治理、想往語義方向轉(zhuǎn)型的朋友本體工程是你繞不開的基本功。核心工具我會(huì)圍繞Protégé展開格式以RDF/OWL為主最后落到Neo4j的落地映射。整套流程我自己走過不止一遍踩過的坑會(huì)在對(duì)應(yīng)位置標(biāo)出來。2. 本體工程到底在解決什么問題2.1 從“數(shù)據(jù)表”思維切換到“概念”思維大部分人第一次做知識(shí)圖譜腦子里裝的還是關(guān)系型數(shù)據(jù)庫那套東西。表、字段、外鍵一對(duì)一、一對(duì)多。這套思維在結(jié)構(gòu)化數(shù)據(jù)里沒問題但一放到知識(shí)圖譜里就會(huì)出問題。原因很簡(jiǎn)單關(guān)系型數(shù)據(jù)庫的schema是給存儲(chǔ)和查詢優(yōu)化的而本體的schema是給語義和推理優(yōu)化的兩者的目標(biāo)根本不一樣。舉個(gè)具體的例子。假設(shè)你要建一個(gè)“企業(yè)知識(shí)圖譜”里面有公司、人、產(chǎn)品三個(gè)核心概念。在關(guān)系型數(shù)據(jù)庫里你可能會(huì)建三張表然后用外鍵關(guān)聯(lián)。但在本體里你要先問公司和組織是什么關(guān)系公司是不是組織的一個(gè)子類人是不是可以同時(shí)是員工和股東產(chǎn)品有沒有可能屬于多個(gè)公司這些問題的答案決定了你的類層次結(jié)構(gòu)怎么設(shè)計(jì)。我自己的經(jīng)驗(yàn)是做本體設(shè)計(jì)時(shí)先別打開Protégé拿一張白紙把領(lǐng)域里所有的名詞先列出來。名詞就是候選的類動(dòng)詞就是候選的對(duì)象屬性形容詞和數(shù)量就是候選的數(shù)據(jù)屬性。這個(gè)“名詞動(dòng)詞法”聽起來土但極其有效。列完之后你會(huì)發(fā)現(xiàn)有些名詞其實(shí)是同一個(gè)概念的不同叫法有些動(dòng)詞其實(shí)指向的是同一個(gè)關(guān)系這時(shí)候再去做歸并和抽象比直接上手建模要靠譜得多。2.2 本體、RDF、OWL三者的關(guān)系別搞混這三個(gè)詞經(jīng)常被混著用但它們的層次是不一樣的。RDF是一個(gè)數(shù)據(jù)模型它規(guī)定了“三元組”這種表達(dá)形式主語-謂語-賓語就這么簡(jiǎn)單。你可以把RDF理解成“句子的語法”它只告訴你句子怎么造不告訴你句子對(duì)不對(duì)。OWL是建立在RDF之上的本體描述語言它提供了更豐富的詞匯來表達(dá)類、屬性、個(gè)體以及它們之間的邏輯關(guān)系。OWL讓你能說“A是B的子類”“X和Y是不相交的”“這個(gè)屬性的定義域是C”這些聲明就是本體的核心內(nèi)容。OWL有不同表達(dá)力的子語言從OWL Lite到OWL DL再到OWL Full表達(dá)力越強(qiáng)推理的復(fù)雜度越高。實(shí)際項(xiàng)目里我一般用OWL DL它在表達(dá)力和可計(jì)算性之間取得了比較好的平衡。本體則是你用OWL或者RDFS寫出來的那套概念體系的統(tǒng)稱。本體是內(nèi)容OWL是寫內(nèi)容的語言RDF是底層的存儲(chǔ)格式。三者是“內(nèi)容-語言-格式”的關(guān)系不是并列關(guān)系。搞清楚這一點(diǎn)你在看Protégé里的各種設(shè)置時(shí)就不會(huì)暈。2.3 本體工程能幫你避開的三個(gè)大坑第一個(gè)坑是實(shí)體粒度不統(tǒng)一。沒有本體約束的情況下有人把“北京市”當(dāng)成一個(gè)實(shí)體有人把“北京市朝陽區(qū)”也當(dāng)成一個(gè)實(shí)體還有人把“北京”和“北京市”當(dāng)成兩個(gè)不同的實(shí)體。本體通過定義類的實(shí)例化規(guī)則和等價(jià)關(guān)系能在建模階段就把粒度問題解決掉。第二個(gè)坑是關(guān)系類型泛濫。我見過一個(gè)圖譜里有“屬于”“隸屬于”“歸屬”“從屬”四個(gè)關(guān)系實(shí)際上表達(dá)的是同一個(gè)意思。本體工程要求你在設(shè)計(jì)階段就明確對(duì)象屬性的集合并且通過屬性層次結(jié)構(gòu)來管理同義關(guān)系避免后期數(shù)據(jù)灌入時(shí)出現(xiàn)關(guān)系爆炸。第三個(gè)坑是推理能力缺失。沒有本體的圖譜就是一個(gè)靜態(tài)的圖你存了什么它就只能查什么。有了本體你可以定義傳遞屬性、對(duì)稱屬性、函數(shù)屬性讓圖譜具備推理能力。比如你定義了“位于”是傳遞屬性那么“A位于BB位于C”就能自動(dòng)推出“A位于C”不需要你手動(dòng)加邊。這個(gè)能力在實(shí)體鏈接和關(guān)系補(bǔ)全場(chǎng)景里非常關(guān)鍵。3. 用Protégé從零搭建本體的完整實(shí)操3.1 環(huán)境準(zhǔn)備與Protégé基礎(chǔ)配置Protégé是斯坦福大學(xué)開發(fā)的本體編輯器開源免費(fèi)目前主流版本是5.6.x。下載地址在官網(wǎng)就能找到Windows、Mac、Linux都有對(duì)應(yīng)的安裝包。安裝過程沒什么好說的一路下一步就行。但有幾個(gè)配置項(xiàng)我建議你在開始建模前就調(diào)好能省掉后面很多麻煩。第一個(gè)是渲染器設(shè)置。Protégé默認(rèn)的渲染器在某些系統(tǒng)上會(huì)顯示異常尤其是中文環(huán)境下。你可以在Preferences里找到Renderer把實(shí)體渲染器改成“Render entities as qname”或者“Render entities as label”。我一般用label渲染因?yàn)榻r(shí)看中文標(biāo)簽比看英文ID直觀得多。第二個(gè)是推理機(jī)配置。Protégé本身不帶推理機(jī)你需要裝一個(gè)插件。最常用的是HermiT和PelletHermiT對(duì)OWL DL的支持比較完整推理速度也還行。在Preferences的Reasoner選項(xiàng)卡里可以設(shè)置默認(rèn)推理機(jī)。裝好之后記得在Reasoner菜單里點(diǎn)一下“Start reasoner”這樣你在建模過程中就能實(shí)時(shí)看到推理結(jié)果比如某個(gè)類被推斷為不可滿足unsatisfiable時(shí)會(huì)標(biāo)紅。第三個(gè)是文件格式。Protégé默認(rèn)保存為OWL/XML格式這個(gè)格式機(jī)器友好但人不好讀。如果你需要版本管理或者人工review建議同時(shí)導(dǎo)出一份Turtle格式.ttl可讀性好很多。我自己的習(xí)慣是主文件用OWL/XML每次提交前導(dǎo)出一份Turtle做diff。注意Protégé的自動(dòng)保存功能默認(rèn)是關(guān)閉的建模過程中一定要養(yǎng)成CtrlS的習(xí)慣。我丟過一次做了三個(gè)小時(shí)的本體從那以后就設(shè)了每5分鐘自動(dòng)保存。3.2 定義類層次結(jié)構(gòu)從頂層本體到領(lǐng)域本體類層次結(jié)構(gòu)是本體的骨架。我的做法是先搭一個(gè)輕量的頂層框架再往里填領(lǐng)域內(nèi)容。頂層框架不需要太復(fù)雜通常包含“實(shí)體”“事件”“屬性”“關(guān)系”這幾個(gè)大類就夠了。有些項(xiàng)目會(huì)直接復(fù)用BFO或者DOLCE這些成熟的頂層本體但對(duì)于大多數(shù)業(yè)務(wù)場(chǎng)景來說自己搭一個(gè)簡(jiǎn)單的頂層反而更靈活。具體操作上在Protégé的Classes標(biāo)簽頁里你可以通過“Add subclass”來創(chuàng)建子類。我建議一開始就把命名規(guī)范定好比如類名用大駝峰Company、Person、Product屬性名用小駝峰hasName、belongsTo實(shí)例名用全小寫加下劃線。這個(gè)規(guī)范看起來是小事但等到圖譜有幾千個(gè)節(jié)點(diǎn)的時(shí)候命名混亂會(huì)讓你痛不欲生。類層次結(jié)構(gòu)的深度也要控制。我見過有人把類層次做到七八層結(jié)果自己都記不住哪個(gè)類在哪個(gè)位置。一般來說三到五層就夠了。第一層是頂層概念第二層是領(lǐng)域大類第三層是具體子類再往下就是實(shí)例了。如果發(fā)現(xiàn)需要更深的層次往往說明你的抽象方式有問題可能需要引入新的維度而不是繼續(xù)往下分。還有一個(gè)實(shí)操技巧在Protégé里可以用“Create class hierarchy from indentation”功能批量導(dǎo)入類結(jié)構(gòu)。你可以先在文本編輯器里用縮進(jìn)寫好類層次然后粘貼進(jìn)去Protégé會(huì)自動(dòng)解析成樹形結(jié)構(gòu)。這個(gè)功能在類數(shù)量多的時(shí)候特別好用比一個(gè)個(gè)點(diǎn)快得多。3.3 對(duì)象屬性與數(shù)據(jù)屬性的設(shè)計(jì)要點(diǎn)屬性設(shè)計(jì)是本體工程里最容易出問題的地方。對(duì)象屬性描述的是類與類之間的關(guān)系數(shù)據(jù)屬性描述的是類與字面量字符串、數(shù)字、日期之間的關(guān)系。兩者的設(shè)計(jì)思路完全不同。對(duì)象屬性設(shè)計(jì)時(shí)首先要確定定義域domain和值域range。定義域是“這個(gè)屬性從哪個(gè)類出發(fā)”值域是“這個(gè)屬性指向哪個(gè)類”。比如“belongsTo”這個(gè)屬性定義域是Product值域是Company意思就是“產(chǎn)品屬于公司”。定義域和值域設(shè)好之后推理機(jī)就能幫你檢查數(shù)據(jù)的一致性如果某個(gè)產(chǎn)品指向了一個(gè)不是公司的實(shí)體推理機(jī)就會(huì)報(bào)錯(cuò)。對(duì)象屬性還有幾個(gè)重要的特性需要根據(jù)業(yè)務(wù)語義來設(shè)置。函數(shù)屬性Functional表示“每個(gè)主體最多只有一個(gè)值”比如“hasCEO”就是函數(shù)屬性一個(gè)公司只能有一個(gè)CEO。逆函數(shù)屬性InverseFunctional表示“每個(gè)值最多被一個(gè)主體指向”比如“hasID”就是逆函數(shù)屬性一個(gè)身份證號(hào)只能對(duì)應(yīng)一個(gè)人。傳遞屬性Transitive表示“關(guān)系可以傳遞”比如“l(fā)ocatedIn”。對(duì)稱屬性Symmetric表示“關(guān)系雙向成立”比如“marriedTo”。這些特性設(shè)對(duì)了推理機(jī)就能幫你自動(dòng)補(bǔ)全很多隱含關(guān)系。數(shù)據(jù)屬性的設(shè)計(jì)相對(duì)簡(jiǎn)單但有一個(gè)坑要注意數(shù)據(jù)類型的選擇。Protégé支持string、integer、float、boolean、dateTime等多種數(shù)據(jù)類型。我建議在建模階段就把數(shù)據(jù)類型定死不要都用string。原因很簡(jiǎn)單如果你把年齡存成string后面想做數(shù)值比較查詢就麻煩了。另外對(duì)于有固定取值范圍的屬性可以用枚舉enumeration來約束比如“status”只能是“active”“inactive”“pending”三個(gè)值之一。提示屬性命名時(shí)加前綴能大幅提升可讀性。對(duì)象屬性用“has”“is”“belongsTo”這類動(dòng)詞開頭數(shù)據(jù)屬性用“has”“is”開頭一眼就能區(qū)分。3.4 個(gè)體、公理與約束的添加方法個(gè)體Individual是類的實(shí)例是本體里最底層的元素。在Protégé的Individuals標(biāo)簽頁里可以創(chuàng)建個(gè)體并指定它屬于哪個(gè)類。個(gè)體創(chuàng)建時(shí)要注意兩點(diǎn)一是命名要唯一且有意義不要用“個(gè)體1”“個(gè)體2”這種二是要盡量填寫數(shù)據(jù)屬性值這些值在后續(xù)映射到圖數(shù)據(jù)庫時(shí)會(huì)直接變成節(jié)點(diǎn)屬性。公理Axiom是本體的靈魂它定義了概念之間必須滿足的邏輯約束。最常見的公理包括子類關(guān)系SubClassOf、等價(jià)關(guān)系EquivalentTo、不相交關(guān)系DisjointWith、屬性約束someValuesFrom、allValuesFrom、minCardinality、maxCardinality。這些公理寫好了推理機(jī)就能幫你做一致性檢查、分類推斷和實(shí)例推斷。我舉一個(gè)實(shí)際場(chǎng)景來說明公理的價(jià)值。假設(shè)你定義了“Employee”和“Company”兩個(gè)類并且定義了“worksFor”這個(gè)對(duì)象屬性。如果你給“worksFor”加上“someValuesFrom Company”的約束那么任何聲明了worksFor關(guān)系的個(gè)體推理機(jī)都會(huì)自動(dòng)把它推斷為Employee類的實(shí)例。這就是推理帶來的自動(dòng)化能力你不需要手動(dòng)給每個(gè)員工打標(biāo)簽。約束的添加要循序漸進(jìn)。一開始不要寫太多復(fù)雜的公理先把基本的類層次和屬性關(guān)系建好跑一遍推理機(jī)看看有沒有報(bào)錯(cuò)。確認(rèn)基礎(chǔ)沒問題之后再逐步添加更精細(xì)的約束。我見過有人一上來就寫了幾十條公理結(jié)果推理機(jī)跑不動(dòng)或者報(bào)了一堆自己看不懂的錯(cuò)最后只能全部刪掉重來。4. 從本體到Neo4j知識(shí)圖譜的落地映射4.1 本體模型到屬性圖模型的轉(zhuǎn)換規(guī)則本體建好了接下來要把它落到圖數(shù)據(jù)庫里。這里有一個(gè)關(guān)鍵認(rèn)知OWL本體和屬性圖Property Graph不是一一對(duì)應(yīng)的中間需要一個(gè)轉(zhuǎn)換層。OWL的類對(duì)應(yīng)屬性圖的節(jié)點(diǎn)標(biāo)簽LabelOWL的個(gè)體對(duì)應(yīng)屬性圖的節(jié)點(diǎn)NodeOWL的數(shù)據(jù)屬性對(duì)應(yīng)節(jié)點(diǎn)的屬性PropertyOWL的對(duì)象屬性對(duì)應(yīng)屬性圖的邊Relationship。但這個(gè)對(duì)應(yīng)關(guān)系有幾個(gè)細(xì)節(jié)需要處理。第一OWL支持多繼承一個(gè)個(gè)體可以同時(shí)屬于多個(gè)類而屬性圖的節(jié)點(diǎn)可以有多個(gè)標(biāo)簽這一點(diǎn)是對(duì)得上的。第二OWL的對(duì)象屬性可以有屬性比如“worksFor”這個(gè)關(guān)系本身有“startDate”屬性但屬性圖的邊雖然理論上可以帶屬性實(shí)際使用中很多圖數(shù)據(jù)庫對(duì)邊屬性的支持有限這時(shí)候就需要把邊升級(jí)為節(jié)點(diǎn)即“關(guān)系實(shí)體化”。第三OWL的推理能力在屬性圖里是沒有的你需要在數(shù)據(jù)導(dǎo)入階段就把推理結(jié)果算好或者在圖數(shù)據(jù)庫層面用查詢來實(shí)現(xiàn)部分推理。我一般的做法是先用Protégé把本體建好然后用SPARQL查詢把推理后的三元組導(dǎo)出來再寫一個(gè)轉(zhuǎn)換腳本把三元組轉(zhuǎn)成Neo4j的Cypher導(dǎo)入語句。這個(gè)流程雖然多了一步但能保證本體里的推理結(jié)果被完整地帶到圖數(shù)據(jù)庫里。4.2 用Python腳本實(shí)現(xiàn)本體到Cypher的自動(dòng)轉(zhuǎn)換手動(dòng)把本體里的每個(gè)類、每個(gè)屬性、每個(gè)個(gè)體都寫成Cypher語句是不現(xiàn)實(shí)的必須自動(dòng)化。我用的是Python的rdflib庫來解析OWL文件然后生成Cypher語句。核心邏輯分三步解析類生成節(jié)點(diǎn)標(biāo)簽約束解析對(duì)象屬性生成關(guān)系類型解析個(gè)體生成節(jié)點(diǎn)和邊。from rdflib import Graph, Namespace, RDF, RDFS, OWL g Graph() g.parse(my_ontology.owl, formatxml) # 提取所有類 classes [] for s in g.subjects(RDF.type, OWL.Class): label g.value(s, RDFS.label) classes.append((s, label)) # 提取所有對(duì)象屬性 obj_props [] for s in g.subjects(RDF.type, OWL.ObjectProperty): domain g.value(s, RDFS.domain) range_ g.value(s, RDFS.range) obj_props.append((s, domain, range_)) # 生成Cypher cypher_lines [] for cls, label in classes: cypher_lines.append(fCREATE CONSTRAINT IF NOT EXISTS FOR (n:{label}) REQUIRE n.uri IS UNIQUE;) for prop, domain, range_ in obj_props: cypher_lines.append(f// Relationship: {prop} from {domain} to {range_})這段代碼只是骨架實(shí)際使用時(shí)要處理命名空間、多語言標(biāo)簽、屬性特性函數(shù)屬性、傳遞屬性等細(xì)節(jié)。但核心思路就是這樣把本體的結(jié)構(gòu)信息提取出來映射成圖數(shù)據(jù)庫的schema。注意Neo4j的標(biāo)簽和關(guān)系類型不支持中文和特殊字符所以在本體建模時(shí)最好給每個(gè)類和一個(gè)英文ID中文放在label屬性里。這樣轉(zhuǎn)換時(shí)用英文ID做標(biāo)簽中文做顯示名兩邊都不耽誤。4.3 數(shù)據(jù)導(dǎo)入后的圖譜質(zhì)量校驗(yàn)數(shù)據(jù)灌進(jìn)去不代表就完事了必須做質(zhì)量校驗(yàn)。我一般從四個(gè)維度來查完整性、一致性、唯一性、連通性。完整性查的是有沒有該有的邊沒連上。比如每個(gè)Product節(jié)點(diǎn)都應(yīng)該有一條belongsTo邊指向Company如果存在沒有這條邊的Product就說明數(shù)據(jù)有問題。這個(gè)可以用Cypher查詢來檢查MATCH (p:Product) WHERE NOT (p)-[:BELONGS_TO]-(:Company) RETURN p.name AS product_name一致性查的是有沒有違反本體約束的數(shù)據(jù)。比如本體里定義了“Person”和“Company”是不相交的那就不應(yīng)該存在一個(gè)節(jié)點(diǎn)同時(shí)是Person和Company。這個(gè)檢查在本體層面用推理機(jī)做在圖數(shù)據(jù)庫層面可以用Cypher做。唯一性查的是有沒有重復(fù)節(jié)點(diǎn)。同一個(gè)實(shí)體被導(dǎo)入了兩次或者同一個(gè)實(shí)體有不同的URI都會(huì)導(dǎo)致圖譜里出現(xiàn)重復(fù)。這個(gè)可以用節(jié)點(diǎn)的關(guān)鍵屬性比如名稱、ID做分組查詢來發(fā)現(xiàn)。連通性查的是圖譜有沒有孤島。如果有一部分節(jié)點(diǎn)和主圖完全不連通要么是數(shù)據(jù)缺失要么是建模時(shí)漏了關(guān)系。這個(gè)用弱連通分量算法就能查出來。5. 實(shí)操中踩過的坑與排查技巧5.1 本體建模階段的典型錯(cuò)誤第一個(gè)典型錯(cuò)誤是類層次過深。我一開始做本體的時(shí)候總覺得分類越細(xì)越好結(jié)果建了一個(gè)七層的類層次到第四層的時(shí)候自己都記不清哪個(gè)類該放哪里了。后來我總結(jié)了一個(gè)原則如果一個(gè)類下面只有一兩個(gè)子類而且這些子類沒有本質(zhì)區(qū)別那就不要往下分了用屬性來區(qū)分就行。第二個(gè)典型錯(cuò)誤是屬性定義域設(shè)得太寬。比如把“hasName”的定義域設(shè)成“Thing”意思是所有東西都可以有名字。這看起來沒問題但實(shí)際上會(huì)讓推理機(jī)無法做有效的類型檢查。正確的做法是把定義域設(shè)成具體的類比如“Person”和“Company”各自定義自己的名稱屬性或者定義一個(gè)“NamedEntity”類作為它們的父類。第三個(gè)典型錯(cuò)誤是忽略命名空間。Protégé默認(rèn)會(huì)給你一個(gè)base URI但很多人建模時(shí)不在意這個(gè)結(jié)果導(dǎo)出的OWL文件里全是自動(dòng)生成的URI可讀性極差。我建議在新建本體時(shí)就設(shè)置一個(gè)有意義的base URI比如“http://yourdomain.com/ontology/yourdomain#”這樣導(dǎo)出的文件里所有實(shí)體都有清晰的URI。5.2 推理機(jī)報(bào)錯(cuò)的常見原因與解決推理機(jī)報(bào)錯(cuò)是新手最頭疼的問題。最常見的報(bào)錯(cuò)是“Unsatisifiable class”意思是某個(gè)類是不可滿足的即不存在任何個(gè)體能同時(shí)滿足這個(gè)類的所有約束。這通常是因?yàn)榧s束之間互相矛盾比如一個(gè)類同時(shí)被約束為“必須有一個(gè)值來自A”和“必須有一個(gè)值來自B”而A和B是不相交的。排查這類問題的方法是在Protégé里點(diǎn)“Explain”按鈕它會(huì)告訴你哪些公理導(dǎo)致了矛盾。然后你逐條檢查這些公理看哪條寫錯(cuò)了或者哪兩條不應(yīng)該同時(shí)存在。另一個(gè)常見報(bào)錯(cuò)是“Inconsistent ontology”意思是整個(gè)本體不一致存在某個(gè)個(gè)體同時(shí)屬于兩個(gè)不相交的類。這個(gè)排查起來更麻煩因?yàn)閱栴}可能出在任何一個(gè)個(gè)體的聲明上。我的做法是先把推理機(jī)關(guān)掉手動(dòng)檢查所有DisjointWith聲明確認(rèn)沒有個(gè)體同時(shí)屬于兩個(gè)不相交的類。然后再開推理機(jī)看是否還有報(bào)錯(cuò)。提示推理機(jī)跑得慢的時(shí)候可以先關(guān)掉“EquivalentTo”和“DisjointWith”這些復(fù)雜公理只保留基本的類層次和屬性關(guān)系跑一遍看看基礎(chǔ)有沒有問題?;A(chǔ)沒問題再逐步加回復(fù)雜公理這樣能快速定位問題。5.3 圖譜查詢性能優(yōu)化的幾個(gè)手段圖譜建好之后查詢性能是繞不開的問題。我總結(jié)下來優(yōu)化手段主要有三個(gè)層次schema層、查詢層、存儲(chǔ)層。Schema層的優(yōu)化主要是加索引和約束。Neo4j里對(duì)經(jīng)常用來做查詢?nèi)肟诘膶傩约铀饕艽蠓嵘樵兯俣取1热缒憬?jīng)常按名稱查節(jié)點(diǎn)那就給name屬性加索引。另外給每個(gè)節(jié)點(diǎn)加一個(gè)唯一性約束比如uri屬性不僅能保證數(shù)據(jù)質(zhì)量還能加速基于該屬性的查詢。查詢層的優(yōu)化主要是避免全圖掃描。Cypher查詢里如果用了沒有索引的屬性做過濾Neo4j會(huì)做全圖掃描數(shù)據(jù)量大的時(shí)候會(huì)非常慢。寫查詢時(shí)盡量從有索引的屬性入手然后用關(guān)系擴(kuò)展來縮小范圍。另外合理使用“PROFILE”命令來分析查詢計(jì)劃看看有沒有可以優(yōu)化的地方。存儲(chǔ)層的優(yōu)化主要是調(diào)整Neo4j的內(nèi)存配置。Neo4j的性能很大程度上取決于頁面緩存page cache的大小。如果圖譜數(shù)據(jù)量大于可用內(nèi)存查詢就會(huì)頻繁讀磁盤速度會(huì)急劇下降。我一般會(huì)把頁面緩存設(shè)成可用內(nèi)存的50%到70%剩下的留給操作系統(tǒng)和查詢執(zhí)行。5.4 常見問題速查表問題現(xiàn)象可能原因排查方法解決方案推理機(jī)報(bào)Unsatisifiable class約束矛盾用Explain功能查看矛盾公理刪除或修改沖突的約束圖譜查詢返回空結(jié)果關(guān)系方向?qū)懛礄z查Cypher里的箭頭方向調(diào)整查詢中的關(guān)系方向?qū)霐?shù)據(jù)后節(jié)點(diǎn)重復(fù)URI不唯一按關(guān)鍵屬性分組統(tǒng)計(jì)合并重復(fù)節(jié)點(diǎn)或修正URI推理機(jī)跑不動(dòng)公理太復(fù)雜逐步減少公理測(cè)試簡(jiǎn)化公理或換用更輕量的推理機(jī)圖譜查詢慢缺少索引用PROFILE分析查詢計(jì)劃給過濾屬性加索引本體文件打不開格式不兼容檢查文件擴(kuò)展名和內(nèi)容轉(zhuǎn)換格式或修復(fù)文件6. 本體版本管理與團(tuán)隊(duì)協(xié)作的實(shí)操建議6.1 用Git管理OWL文件的正確姿勢(shì)OWL文件本質(zhì)上是文本文件完全可以用Git做版本管理。但直接管理OWL/XML格式的diff可讀性很差因?yàn)閄ML的標(biāo)簽嵌套很深稍微改一個(gè)地方就會(huì)產(chǎn)生大量diff。我的做法是主文件用OWL/XML但每次提交前用Protégé導(dǎo)出一份Turtle格式的文件Turtle的diff可讀性好很多review的時(shí)候看Turtle文件就行。Git的commit message也要有規(guī)范。我一般用“feat: 添加Product類”“fix: 修正belongsTo的定義域”“refactor: 重構(gòu)類層次結(jié)構(gòu)”這樣的格式和代碼提交的規(guī)范保持一致。這樣回溯歷史的時(shí)候能快速定位到某次修改。還有一個(gè)細(xì)節(jié)Protégé保存文件時(shí)會(huì)改變文件的內(nèi)部排序?qū)е录词箖?nèi)容沒變diff也會(huì)顯示大量變化。解決方法是每次保存后先跑一遍格式化工具比如rdflib的序列化把文件內(nèi)容規(guī)范化然后再提交。這樣diff就只顯示真正的語義變化了。6.2 多人協(xié)作時(shí)的命名沖突解決多人同時(shí)編輯一個(gè)本體時(shí)命名沖突是最大的問題。兩個(gè)人可能給同一個(gè)概念起了不同的名字或者給不同的概念起了相同的名字。解決這個(gè)問題的關(guān)鍵是建立命名規(guī)范并嚴(yán)格執(zhí)行。我的做法是在項(xiàng)目開始時(shí)就建一個(gè)“命名約定”文檔規(guī)定類名、屬性名、實(shí)例名的命名規(guī)則和命名空間前綴。每個(gè)人在創(chuàng)建新實(shí)體之前先搜索一下是否已經(jīng)存在類似的實(shí)體。Protégé的搜索功能可以按名稱和URI搜索用起來很方便。如果沖突已經(jīng)發(fā)生了解決方法是先找出所有沖突的實(shí)體然后確定哪個(gè)是“正確”的把另一個(gè)的引用全部改成正確的最后刪除錯(cuò)誤的實(shí)體。在Protégé里可以用“Refactor”菜單下的“Rename entity”功能來批量修改引用比手動(dòng)改要安全得多。6.3 本體文檔化與知識(shí)傳承本體建完之后如果不寫文檔過三個(gè)月自己都看不懂。文檔不需要很復(fù)雜但必須包含幾個(gè)核心內(nèi)容類的定義和用途、屬性的語義和約束、典型的使用示例、版本變更記錄。我一般會(huì)在本體文件里用rdfs:comment給每個(gè)類和屬性加注釋這些注釋在Protégé里可以直接看到也能導(dǎo)出成文檔。另外我會(huì)單獨(dú)維護(hù)一個(gè)Markdown格式的“本體設(shè)計(jì)說明”記錄設(shè)計(jì)決策的背景和理由。比如“為什么把Company和Organization分開”“為什么belongsTo是函數(shù)屬性”這些決策背后的思考比本體本身更有價(jià)值。知識(shí)傳承方面我建議每個(gè)季度做一次本體review檢查是否有過時(shí)的類或?qū)傩孕枰獜U棄是否有新的業(yè)務(wù)需求需要擴(kuò)展。Review的時(shí)候拉上業(yè)務(wù)方一起確保本體和實(shí)際業(yè)務(wù)保持一致。本體不是建完就完了它是一個(gè)活的資產(chǎn)需要持續(xù)維護(hù)。