📘 第2章 规则系统及其变体
📄 一句话概括

专家系统是规则系统穿上工程外衣的样子:规则库、推理机、解释器、界面,四块拼起来, 卖给了医院和工厂。它在商业上真赚到了钱,是 1980 年代 AI 唯一有客户的形态。 这一节的重点不在结构,在「不确定」怎么处理。 MYCIN 那套置信度公式只有三行,但它做对了一件事、也留下了两个坑。

📝 本节和前面几节的关系

上一节的规则系统只会说「是」或「不是」。 可医生从来不说「是」,他说「八成是」。这一节补的就是这「八成」。 CF 这套东西后来被贝叶斯网络取代,第 6 章会讲贝叶斯那一支怎么算。

一、四个零件拼成一个能对话的系统

专家系统和上一节的规则系统,零件几乎一样,但多了一个关键角色。

零件 干什么 少了它会怎样
知识库 存规则和事实 什么都推不出
推理机 匹配规则、推进结论 知识是死的
解释器 回答「你怎么得出这个结论」 没人敢用
人机界面 用医生的词问、用医生的词答 医生不愿用
第三块是专家系统真正的卖点。用户问「为什么说要用抗生素」,系统能把整条链摊开:
「因为白细胞升高、咳嗽有痰,所以判断细菌感染;因为细菌感染,所以建议用抗生素。」
这一段话今天看很朴素,但它是深度网络给不出的东西。一个几十层的网络告诉你「这张片子有问题」,
你没法问它「你凭什么这么看」。
第四块也常被低估。
MYCIN 的问诊顺序、用词、甚至「不知道」怎么说,都是按临床习惯设计的。
一个准确率 90% 但要求医生填 60 个字段的系统,没人会用。

二、不确定从三个地方来

第一,输入本身不确定。病人说「有点头晕」,这个「有点」没法量化; 体温 37.8 度是刚量的还是两小时前量的,意义不一样。 第二,规则本身不确定。专家说「咳嗽有痰多半是细菌感染」, 这个「多半」是他二十年经验的一个模糊总结,不是统计数字。 第三,检查有误差。白细胞计数有仪器误差,培养皿可能被污染。 三条加起来,一个只会说「是/不是」的系统根本没法用。它必须能输出「我有七成把握」。 早期试过用概率。 但用概率要写出 P(症状|疾病),还得保证疾病之间互斥且完备,临床上根本拿不到这些数。1975 年, 肖特利夫和布坎南在 MYCIN 里换了一套自己的记账法,叫置信度因子 CF。

三、CF 的三行公式

CF 取值 -1 到 +1。 +1 是确定成立,-1 是确定不成立,0 是毫无信息。规则带一个 CF,事实也带一个 CF, 推的时候两者相乘。 多条证据指向同一个结论时,要合并。公式分三个分支:

同为正: c = c1 + c2 * (1 - c1)
同为负: c = c1 + c2 * (1 + c1)
一正一负:c = (c1 + c2) / (1 - min(|c1|, |c2|))

第一个分支最好理解:新证据只能填补「剩下的不确定」。已经有 0.6 的把握,剩下 0.4 不确定, 新证据最多填 0.4 的 60%,于是变成 0.84。 第三个分支是「正负相抵」。分母那个 1 - min(|c1|, |c2|) 是「两边重合的那部分不算数」, 所以抵消得比简单相加更彻底。 这一套的关键性质是:它可交换、可结合。三条证据不管按什么顺序合并, 结果一样(Demo 里六种排列全是 0.75)。 这不是巧合,是公式特意凑出来的——不然推理结果会依赖规则的书写顺序, 那专家系统的答案就没法复现。

四、反证的权重不对称地大

Demo 第四段那张表是这一节最该记住的。 一条 -0.7 的反证,能把 0.3、0.5、0.6 的正证全部翻成负数;正好 0.7 才打平;0.8 以上才压得住。 分界线为什么是 0.7? 因为异号那个公式的分子是 c1 + c2,要让它大于 0,就得 c1 大于 |c2|。就这么直白。 这个性质在医学上是对的。一条「培养出病毒」的证据, 确实应该压过好几条「症状像细菌感染」的证据——后者是间接的,前者是直接的。 但它也埋了一个坑:CF 没有「证据之间是否独立」的概念。三条其实来自同一个检验的「证据」, 会被当成三条独立证据累加,把 CF 推到 0.94 以上。这在临床上就是过度自信。

五、为什么被统计方法取代

CF 有两个硬伤。 第一,那些数字是人给的,不是从数据里估的。「咳嗽有痰 → 细菌感染,CF 0.7」这个 0.7 是专家拍的, 换一个专家可能拍 0.6,换一家医院可能拍 0.5。系统继承了专家的判断,也继承了专家的偏差。 第二,CF 不是概率,不能拿来做决策。想知道「用抗生素的期望收益」,得算 P(细菌感染), 而 CF 换算不出这个概率。它只能排序,不能算账。 1988 年,珀尔把贝叶斯网络系统化, 让「不确定」第一次有了严格的概率语义,而且可以从数据里学。加上 1990 年代统计学习起来, CF 就退到教科书里了。 但别把这段读成「CF 是错的」。它解决的是「在拿不到先验概率的年代, 怎么让系统输出一个带程度的答案」。那个年代只有这一条路,它跑通了,而且真上了临床。 这一段的历史位置值得记住:规则系统教会机器「按人的逻辑推」,CF 教会机器「说自己有多确定」。 下一节讲推荐引擎,它把这两件事一起交给了数据。

六、动手 Demo:一套上呼吸道感染的规则,和 MYCIN 那三行合并公式

六条规则、四个症状、两个病、一个处置。规则和事实都带 CF,推理时规则 CF 乘以前提 CF, 同一结论的多条证据按 MYCIN 公式合并。 代码里 cf_add 就是那三行公式,combine 只是把它串起来。 没有训练、没有数据,所有数字都是人手填的。

def cf_add(c1, c2):
    """MYCIN 的置信度合并:同正、同负、异号,三个分支。"""
    if c1 >= 0 and c2 >= 0:
        return c1 + c2 * (1.0 - c1)      # 剩下的不确定再打个折
    if c1 < 0 and c2 < 0:
        return c1 + c2 * (1.0 + c1)
    return (c1 + c2) / (1.0 - min(abs(c1), abs(c2)))   # 正负相抵

def combine(cs):
    """一串证据从 0 起累加。"""
    v = 0.0
    for c in cs:
        v = cf_add(v, c)
    return v

# 规则 =(前提, 结论, 置信度)
RULES = (((u'发烧',),          u'感染',     0.6),
         ((u'咳嗽', u'有痰'),  u'细菌感染', 0.7),
         ((u'咳嗽', u'无痰'),  u'病毒感染', 0.6),
         ((u'白细胞升高',),    u'细菌感染', 0.5),
         ((u'细菌感染',),      u'用抗生素', 0.8),
         ((u'病毒感染',),      u'用抗生素', -0.7))

print(combine([0.6] * 3))        # 0.936
print(cf_add(0.6, -0.7))         # -0.25,一条反证就翻过去

实跑输出:

--------------------------------------------------------------------
专家系统的置信度
--------------------------------------------------------------------

==============================================================



[1] 规则不只是「若 A 则 B」,还带一个可信程度



    前提                  结论          CF

    发烧                  感染          +0.6

    咳嗽 且 有痰          细菌感染      +0.7

    咳嗽 且 无痰          病毒感染      +0.6

    白细胞升高            细菌感染      +0.5

    细菌感染              用抗生素      +0.8

    病毒感染              用抗生素      -0.7



  CF 取值 -1 到 +1:+1 是确定成立,-1 是确定不成立,0 是毫无信息。

  它和概率不是一回事——CF 不满足概率公理,它只是一个「可信程度」的记账法。



[2] 一次推理:从症状到用药建议



    症状                  细菌感染    病毒感染    用抗生素

    只有发烧              +0.00       +0.00       +0.00

    发烧 + 咳嗽有痰       +0.70       +0.00       +0.56

    再加白细胞升高        +0.85       +0.00       +0.68

    换成干咳              +0.00       +0.60       -0.42

    干咳 + 白细胞升高     +0.50       +0.60       -0.03



  第一行全是 0.00——0 在这里的意思是「毫无信息」,不是「确定不成立」。

  只有发烧时,系统只知道「感染」,「感染」推不出细菌还是病毒,只能停在 0。



  第三行是最典型的:细菌感染的证据累到 +0.85,

  于是「用抗生素」被推到 +0.68。

  第四行换了干咳,病毒感染的证据上来,抗生素的 CF 立刻被拉低。



[3] 同向证据叠加:越多越接近 1,但永远到不了 1



    证据条数    CF          离 1 还差

    1           0.6000      4.00e-01

    2           0.8400      1.60e-01

    3           0.9360      6.40e-02

    5           0.9898      1.02e-02

    10          0.9999      1.05e-04

    20          1.0000      1.10e-08



  每加一条 0.6 的证据,剩下的不确定就乘以 0.4。

  20 条之后还差 1.10e-08——数学上永远填不满,

  这跟贝叶斯不一样:贝叶斯里证据足够多,后验可以任意逼近 0 或 1。

  CF 这么设计是有意的——它承认「没见到反证」不等于「确定成立」。



[4] 一条反证,要几条正证才压得住



    正证 CF     叠加 -0.7 之后    结果

    +0.3        -0.5714           翻成负数

    +0.5        -0.4000           翻成负数

    +0.6        -0.2500           翻成负数

    +0.7        +0.0000           刚好抵消

    +0.8        +0.3333           还是正的

    +0.9        +0.6667           还是正的



  分界线正好在 0.7:正证小于 0.7,一条 -0.7 就把它整个翻过去。

  这是这一节最该记住的一条:反证的权重不对称地大。

  一条「病毒感染」的证据,能顶掉好几条「细菌感染」的证据。



[5] 换个顺序算,结果一样吗



  三条证据 +0.7、+0.5、-0.4,六种排列全部算一遍:

    得到的最终 CF 有 1 种,值 = 0.750000

  合并公式被设计成可交换、可结合的——不然推理结果会依赖规则的书写顺序,

  那专家系统的答案就没法复现了。

  这一点值得和上一节对比:那个规则系统里,规则顺序不影响结果,

  是因为前向链是单调的;这里的 CF 公式能保证顺序无关,是数学上特意凑出来的。



[6] 和「非黑即白」比一比



    写法            结论              遇到缺失信息时

    布尔规则        是/否            推不出来,沉默

    置信度          -1 到 +1 之间     给一个偏低的分数,还能用



  上一节说的「系统不会报错,它只是沉默」,在 CF 这里缓解了一半:

  证据不全时它给出的不是沉默,是一个带符号的分数:

  表里最后那个 -0.03,意思是「两边的证据几乎抵消了」,

  这比「推不出来」多了一点信息——它至少告诉你「有人提了反证」。

  但代价是:那个 0.3 从哪来,谁也说不清。

  CF 是人给的,不是从数据里估的——这正是专家系统后来被统计方法取代的原因之一。

第一段先把规则摆出来。注意最后一条的 CF 是负的——「病毒感染 → 用抗生素」给 -0.7, 意思是「如果是病毒感染,就别用抗生素」。规则系统靠「加一条负规则」来表达反对。 第二段跑五个病例。第一行全是 0.00,这个 0 的意思是「毫无信息」,不是「确定不成立」。 只有发烧时,系统只知道「感染」,推不出细菌还是病毒,只能停在 0。 第三行是标准案例:有痰 + 白细胞升高,细菌感染累到 0.85,抗生素被推到 0.68。 这两个数的关系是 0.8 乘 0.85——规则 CF 乘以前提 CF,这就是「不确定会往下传」。 最后一行最有意思:干咳 + 白细胞升高,细菌感染 0.50、病毒感染 0.60,两边几乎抵消, 抗生素只剩 -0.03。系统给出的不是「不知道」,是「两边都有证据,我倾向于不用」。 这个细微差别,布尔规则给不出来。 第三段量叠加。一条 0.6 是 0.6,两条 0.84,三条 0.936,五条 0.9898。 每加一条,剩下的不确定就乘 0.4。 表格最后两行的「离 1 还差」是 1.05e-04 和 1.10e-08。 数学上永远填不满。这跟贝叶斯不一样——贝叶斯里证据足够多,后验可以任意逼近 0 或 1。 CF 这么设计是有意的:它承认「没见到反证」不等于「确定成立」。 第四段是这一节的重点。正证 0.3、0.5、0.6 遇上 -0.7 全部翻负;0.7 正好抵消;0.8 才留 0.3333。 分界线就在 0.7,因为异号公式的分子是 c1 + c2。 第五段验证可交换性。三条 CF 六种排列,结果都是 0.75,只有 1 种值。 这一步在工程上是必需的:不然同一个病例换个录入顺序,结论就变了,没人敢用。 第六段做个收尾对比。布尔规则遇到缺失信息是沉默,CF 给一个带符号的分数。 多出来的信息是真的,但那个分数从哪来,谁也说不清。这是它后来被取代的根子。

30 秒实验

  1. 把最后那条规则的 CF 从 -0.7 改成 -0.4,重跑第二段。 看第五行怎么变——原来 -0.03 应该变成明显的正数。这就是「反证有多硬」对结论的影响。
  2. 给 combine 传 [0.6, 0.6, 0.6, 0.6, 0.6] 和 [combine([0.6]*5)],看两者是否相等。 前者是逐条累加,后者是「已经合并好的结果再合并」,结果应该一样——这就是结合律。
  3. 构造 combine([-0.6, -0.6, -0.6]),看负向叠加对称不对称。 对称的,值应该是 -0.936。不对称就说明公式抄错了。

常见误区

  1. 以为 CF 是概率。CF 不满足概率公理:三条 CF 0.6 的证据合并成 0.936, 可如果你按概率算「至少一条成立」, 那是 1 - 0.4 的三次方 = 0.936——两个结果碰巧一样,但含义完全不同。CF 说的是「我的把握」, 概率说的是「事件发生的频率」。
  2. 以为反证和正证权重一样。异号公式的分母把「两边重合的部分」扣掉了, 所以抵消得比简单相加更彻底。 一条 -0.7 能压过 0.6,这在直觉上是对的:一条直接证据比三条间接证据硬。
  3. 以为专家系统的失败是因为准确率不够。MYCIN 在测试里准确率约 69%,超过当时的一般医生。 它失败在「搬到第二个科室」,而且规则库维护成本随条数平方长。准确率不是问题。
  4. 以为 CF 已经死了。它的思路活在很多地方:搜索引擎的相关度打分、风控模型的规则分数、 甚至大模型输出的置信度。「给个带符号的分数」这件事,就是从 CF 这一支传下来的。
自测三问(点开对答案) 1. MYCIN 为什么不用概率,改用 CF? —— 因为用概率要写 P(症状|疾病), 还得保证所有疾病互斥且完备—— **这些数字在 1970 年代的临床上拿不到,而且疾病清单会变。** CF 只需要专家给一个「有多大把握」,一个人就能拍板,代价是丢失了概率语义, 不能拿来做期望收益计算。 2. 为什么一条 -0.7 能压过 0.6 的正证? —— 因为异号合并的公式是 `(c1 + c2) / (1 - min(|c1|, |c2|))`, **分子是简单相加,所以 c1 必须大于 |c2| 才能保持正号。**0.6 + (-0.7) = -0.1,负的; 0.7 + (-0.7) = 0,刚好抵消。**分母只是把幅度放大,不改变符号。** 3. CF 的两个硬伤是什么? —— 一,**数字是人给的,不是从数据估的**——换个专家、换家医院,同一个 CF 就变了, 系统继承判断也继承偏差。 二,**它不是概率,不能算期望**——只能给结论排序,不能回答「这么做划不划算」。 这两条合起来,就是它被贝叶斯网络取代的原因。

专家系统把「怎么推」和「有多确定」都解决了,可它还是得有人写规则。 下一节讲推荐引擎——它是第一类「规则从数据里自己长出来」的系统。

相关笔记