4. SWRL规则语言:从语法到药物实战
讲知识图谱推理,绕不开SWRL。这玩意儿全称是 Semantic Web Rule Language,说白了就是给OWL本体加了一套“如果…那么…”的规则引擎。我最早接触它是在一个药物靶点预测项目里,当时被一堆DL Safe axioms搞得头大,后来发现SWRL规则能三行搞定同样的逻辑——嗯,从那以后我就成了SWRL的忠实用户。
4.1 SWRL的骨架:Atom和Imp
SWRL规则长什么样?核心就两个东西:Atom(原子)和Imp(蕴含)。Imp就是一条规则,它把前提(body)和结论(head)用箭头连起来。前提里可以放多个Atom,结论里可以放一个或多个Atom。每个Atom描述一个事实,比如“某个体属于某个类”或者“某两个个体之间存在某个关系”。
举个例子,药物图谱里常见的规则:
Drug(?d) ∧ treats(?d, ?c) → CandidateDrug(?c)
这条规则的意思是:如果某个个体?d是Drug类的实例,并且它和?c之间存在treats关系,那么?c就是一个候选药物(CandidateDrug类的实例)。这里的Drug(?d)、treats(?d, ?c)、CandidateDrug(?c)都是Atom。
Imp的格式很直白:
Body → Head
Body里可以放多个Atom,用∧连接。Head里也放一个或多个Atom。我在项目中见过三十多个Atom堆在Body里的规则,说实话可读性很差。我的习惯是一条规则的Atom数控制在5个以内,超过就考虑拆成多条规则或者用中间推理节点。
4.2 Atom的五大类型
Atom不是随便写的,SWRL标准定义了五种。我整理了一张表,对照着看更清楚:
| Atom类型 | 语法示例 | 语义 |
|---|---|---|
| 类原子 | Drug(?x) |
个体?x属于类Drug |
| 属性原子 | hasTarget(?x, ?y) |
个体?x和?y之间存在hasTarget关系 |
| 同/异原子 | sameAs(?x, ?y) |
?x和?y是同一个个体 |
| 数据值原子 | swrlb:greaterThan(?v, 5.0) |
数据值?v大于5.0 |
| 内置原子 | swrlb:add(?z, ?x, ?y) |
?z是?x和?y相加的结果 |
这里面最常用的是类原子和属性原子。数据值原子和内置原子往往跟数字计算挂钩,在药物图谱里做ADMET筛选时经常用到。
核心要点:SWRL的推理本质就是模式匹配。你的规则前提越精确,推理结果越可靠。我见过不少人把OWL类的层级关系写进规则里,其实大部分继承关系通过OWL推理器就能自动推出来,没必要在SWRL里重复。
4.3 内置函数:给规则加点算力
光靠类原子和属性原子,SWRL能干的事有限。内置函数是SWRL的“计算单元”,帮你做比较、数学运算、字符串处理等。标准内置函数的命名空间是swrlb:。
我按应用场景把常见内置函数分了组:
- 比较类:
swrlb:equal、swrlb:notEqual、swrlb:greaterThan、swrlb:lessThan、swrlb:greaterThanOrEqual、swrlb:lessThanOrEqual - 数学运算:
swrlb:add、swrlb:subtract、swrlb:multiply、swrlb:divide、swrlb:pow、swrlb:abs - 字符串处理:
swrlb:stringConcat、swrlb:contains、swrlb:matches(正则匹配) - 日期和时间:
swrlb:yearMonthDuration、swrlb:dateTime等,用得不多 - 布尔逻辑:
swrlb:booleanNot,能取反
举个实战例子。在药物重定位场景里,我们经常要筛选“口服生物利用度大于30%且半衰期大于4小时”的候选化合物。用SWRL写出来就是:
Compound(?c) ∧ hasOralBioavailability(?c, ?bio) ∧ swrlb:greaterThan(?bio, 30.0)
∧ hasHalfLife(?c, ?hl) ∧ swrlb:greaterThan(?hl, 4.0)
→ GoodOralCandidate(?c)
我的个人习惯:先把数值边界条件用内置函数写清楚,再上类原子。因为数值过滤能大幅减少中间变量的数量,推理性能会好很多。我曾经在一个包含10万+个体的图谱里,光靠调整内置函数的顺序就把推理时间从40分钟降到了6分钟。
4.4 药物图谱典型规则示例
光讲理论没意思,咱们直接上几个我在实际项目中写过的规则。这些规则可以原样丢进Protégé的SWRLTab里跑。
示例1:基于靶点相似性的药物重定位
Drug(?d1) ∧ Drug(?d2) ∧ targets(?d1, ?t) ∧ targets(?d2, ?t) ∧ differentFrom(?d1, ?d2)
∧ treats(?d1, ?disease)
→ mayTreat(?d2, ?disease)
这条规则的意思是:如果两个药物作用于同一个靶点,且其中一个已经用于治疗某疾病,那么另一个药物也可能适用于该疾病。逻辑上没绝对保证,但足够生成候选假设。
示例2:药物相互作用(DDI)预警
Drug(?d1) ∧ Drug(?d2) ∧ metabolizedBy(?d1, ?enzyme)
∧ metabolizedBy(?d2, ?enzyme) ∧ inhibits(?d1, ?enzyme)
→ potentialDDI(?d1, ?d2)
这里用到了同一个酶被两个药物代谢,其中一个还抑制这个酶。结果就是另一个药物在体内浓度可能飙升——典型的DDI场景。
示例3:符合Lipinski五规则的类药分子
Molecule(?m) ∧ hasMolecularWeight(?m, ?mw) ∧ swrlb:lessThan(?mw, 500.0)
∧ hasLogP(?m, ?logp) ∧ swrlb:lessThan(?logp, 5.0)
∧ hasHBondDonors(?m, ?hbd) ∧ swrlb:lessThan(?hbd, 5)
∧ hasHBondAcceptors(?m, ?hba) ∧ swrlb:lessThan(?hba, 10)
→ DrugLike(?m)
你看,四个条件全部用内置函数表达,简洁直观。
我踩过的坑:SWRL规则里不能用OWL的集合操作符(如unionOf、intersectionOf)。你千万不要试图在规则里写类似Drug ⊔ Placebo(?x)这种写法,SWRL不支持。遇到这种场景,你得先在OWL里定义好派生类,再在规则里引用类名。
4.5 SWRL推理的整体逻辑
下面这张图是我自己画的SWRL规则推理流程。你看一遍应该就能明白它在知识图谱里是怎么运作的。
这张图从左到右展示了推理过程:先加载本体和规则集合,然后引擎对Body里的每个Atom做模式匹配,绑定变量;接着通过内置函数过滤掉不满足数值条件的组合;最后对于所有满足条件的绑定,将Head中的Atom实例化并添加为新三元组。整个过程会不断迭代,直到不再产生新的事实为止。
4.6 实战中的几个关键点
最后聊点工程视角的东西。写SWRL规则时,有几个地方特别容易出问题:
- 变量命名:尽量用有语义的前缀,比如
?drug、?target、?disease,别用?x、?y1这种。六条规则以上你就会后悔。 - 空值处理:SWRL不支持OQL那种OPTIONAL。如果某个属性对部分个体缺失,规则会自动跳过不匹配。我曾经因为这个漏掉了一批数据,折腾了两天才发现是某个化合物的logP值没录入。
- 性能监控:规则数量超过50条时,建议用Pellet或者HermiT推理器的增量模式,或者用Drools这样的RETE引擎跑SWRL。Protégé里的默认推理器在数据量大时容易卡死。
- 调试手段:在开发阶段可以先用小数据集验证规则逻辑。我习惯在Protégé里手动创建几个测试个体,敲一遍规则看断言是否准确,再放大到全量数据。