想象你是一家餐厅的老板,想评出本季度的“最佳新菜”。你请来一群评委,让他们依次品尝候选菜品,每一项菜谱都包含食材清单与工序步骤(哪些材料必须先准备、哪一步依赖哪一步)。评委并不知道厨师是谁,只按两个标准打分:好不好吃(这道菜与评委口味的匹配程度)和精不精炼(步骤是否简洁、有无多余环节)。最终,得分最高的菜谱胜出——你在没有看到任何厨房操作的情况下,仅凭“打分表”就反推出了最合理的做菜流程。 贝叶斯网络结构学习中的“基于分数方法”,做的正是这件事。数据就像评委,候选的网络结构就像菜谱,评分函数(Score Function)就是那张打分表。我们从数据出发,为每张候选的“依赖关系图”打分,再通过搜索找到分数最高的那张图。它是概率图模型与因果发现之间的重要桥梁,广泛应用于医疗诊断、风险建模与决策支持等领域的概率推理任务。

岗位关联

掌握基于分数的结构学习,意味着你能从数据中“反推”变量间的依赖骨架,为后续的因果推断、概率推理与决策支持提供可靠基础——这是 AI 从业者从“预测”走向“解释”的关键一步。

目录
  1. 可视化演示
  2. 贝叶斯网络
  3. 结构学习本质
  4. 评分函数框架
  5. BIC评分详解
  6. 贪婪搜索算法
  7. 可识别性讨论
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态展示基于分数方法的贝叶斯网络结构学习过程:从数据出发,生成候选边操作(添加/删除/反转),计算BIC评分,接受或拒绝操作以逐步逼近最优DAG拓扑。

本节配有交互动效(进入视口后自动加载,约 3KB)。

贝叶斯网络结构学习 DAG节点/边 数据流 候选操作 最优评分 Score(D) = LL(D|G) - λ|G| 结构学习通过评分函数搜索最优DAG拓扑

贝叶斯网络

“做菜评比”的第一步,是先看懂一张菜谱长什么样。一张完整菜谱包含食材清单与操作工序,其中工序之间常有依赖关系:“必须先切好葱姜蒜,才能起锅烧油”;“必须等水烧开,才能下饺子”。如果把这些依赖关系画成一张图——每个食材/工序是节点,每条“必须先做 A 才能做 B”画一条从 A 指向 B 的箭头——你就得到了一张 有向无环图(DAG)。它之所以必须“无环”,是因为真正的做菜工序不会出现“先有 A 才有 B,又先有 B 才有 A”的死循环。

贝叶斯网络就是把数据世界中的“菜谱”形式化:每个节点是一个随机变量,箭头表示条件依赖关系,且整张图必须是有向无环图。除了拓扑结构,每个节点还附带一张 条件概率表(CPT),记录“在给定父节点取值时,本节点各取值的概率”。比如“是否下雨”影响“地面是否潮湿”,CPT 就写着:下雨时地面潮湿的概率是 0.9,不下雨时只有 0.1。

有了 DAG 与 CPT,整个变量集合的联合概率分布就可以被拆解为一系列局部概率的乘积。这正是贝叶斯网络最优雅之处:原本需要一张庞大表格才能描述的联合分布,被压缩成一组小表格的组合。好比一份满汉全席的完整做法,被拆成几十张独立的小卡片,每张卡片只描述一道菜的操作,合在一起却能复现整桌宴席。

这份“菜谱总纲”意味着:我们不需要记住所有变量的所有组合概率,只需要记住每道菜在自己前置条件下的表现。理解了这张图,就为下一步“从数据反推菜谱”准备好了舞台。

---

P(X1,X2,…,Xn)=∏i=1nP(Xi∣Pa(Xi))\mathbb{P}(X_1, X_2, \dots, X_n) = \prod_{i=1}^{n} \mathbb{P}(X_i \mid \mathrm{Pa}(X_i))
联合分布等于每个节点在给定其父节点条件下的概率之积。这就是贝叶斯网络的“菜谱总纲”。
  1. 联合分布表示所有变量同时出现的概率,好比一桌菜完整呈现在评委面前的总体验。
    P(X1,X2,…,Xn)\mathbb{P}(X_1, X_2, \dots, X_n)
  2. 连乘符号把每一道菜(每个变量)的“条件概率”逐一相乘,就像把每张小卡片上的步骤合起来构成整本菜谱。
    ∏i=1n\prod_{i=1}^{n}
  3. 条件概率在已知“父节点”取值的条件下,当前节点的概率分布。好比“先切葱姜蒜”这个前置条件确定后,“起锅烧油”的做法才成立。
    P(Xi∣Pa(Xi))\mathbb{P}(X_i \mid \mathrm{Pa}(X_i))

结构学习本质

现在,评委们面前摆着一大桌菜,却没有菜谱——他们只能通过品尝每一道菜,反推食材之间的搭配关系。这正是结构学习的核心任务:从观测数据中恢复出最可能的 DAG 结构。数据是“菜的味道”,结构是“菜的配方”,我们要根据味道猜配方。

问题在于,候选配方的数量爆炸式增长。n 个变量的可能 DAG 数量随 n 呈超指数增长,10 个变量时就有约 \(4.2 \times 10^{18}\) 种结构。想逐张试吃每一份菜谱,在计算上完全不可行,这是一个 NP 难问题。因此,结构学习必须分为两步:设计一把“尺子”(评分函数),以及设计一套“高效试吃策略”(搜索算法)。

基于分数的方法思路很直接:定义一把尺子 \( \mathrm{Score}(G \mid D) \),衡量“结构 G 在数据 D 下的优劣程度”,然后通过搜索算法寻找评分最高的结构。评委试吃时不会做满汉全席,只用小份试吃——评分函数只依赖数据的充分统计量(如计数),可以快速计算。这把尺子必须同时回答两个问题:这道菜好不好吃(拟合数据的程度)?这道菜动不动辄十页纸的冗余步骤(模型复杂度)?

试吃策略再高效,也离不开一把好尺子。如果尺子本身量不准“好吃”与“精炼”,再快的搜索也只是在错误方向上狂奔。

---

G^=arg⁡max⁡G∈GScore(G∣D)\hat{G} = \arg\max_{G \in \mathcal{G}} \mathrm{Score}(G \mid D)
最优结构等于在合法结构空间里让评分函数最大的那个结构。评委在所有候选菜谱中选总分最高者。
  1. 评分函数衡量给定数据下某张图好坏的标准。好比评委对一份菜谱的综合打分。
    Score(G∣D)\mathrm{Score}(G \mid D)
  2. 结构空间所有合法 DAG 的集合。相当于所有符合“无环工序”原则的菜谱全集。
    G\mathcal{G}
  3. 最大化在所有候选结构中挑出打分最高的,即“选总分最高者胜出”。
    arg⁡max⁡G\arg\max_{G}

评分函数框架

如何设计这把“尺子”?从概率的视角看,最自然的想法是:我们希望找到一张图,使得在给定该图结构时,观测数据出现的概率最大。这个概率称为 似然(Likelihood)。一道菜谱解释数据解释得越好,似然越高——评委吃得越满意。

但这里藏着一个陷阱:越复杂的菜谱(边越多、参数越细)往往越能精确复现已有口味,却也越容易“过度拟合”——对新口味完全失去预测力。好比一份菜谱写满了极端细节:“盐 3.17 克,翻炒 2 分 38 秒”,看似精确,换个灶台就彻底失效。因此评分函数必须在拟合度之外加入 复杂度惩罚项:口味好是加分,菜谱冗长是减分。

更加统一的视角来自贝叶斯定理:把结构先验与数据似然结合,得到结构的后验概率。评分函数本质上是在近似最大化后验概率。如果我们对结构没有特别偏好(均匀先验),后验大小就主要取决于边际似然;加入先验后,我们还可以表达“这一步工序更合理”之类的领域知识。由此,一个通用的评分函数框架可以写成:

这一减一加之间,评委完成了从“口味偏好”到“综合评判”的转化。接下来要解决的是:惩罚项具体怎么算?

---

Score(G∣D)=log⁡P(D∣G)−Pen(G)\mathrm{Score}(G \mid D) = \log \mathbb{P}(D \mid G) - \mathrm{Pen}(G)
结构评分 = 数据拟合的对数似然 − 复杂度惩罚。好吃程度减去冗余步骤的代价。
  1. 对数似然数据在结构 G 下的对数概率,值越大表示拟合越好。相当于“口味符合程度”的量化分数。
    log⁡P(D∣G)\log \mathbb{P}(D \mid G)
  2. 复杂度惩罚惩罚参数过多、结构过复杂的图,防止“过度拟合”。相当于评委对冗长菜谱的扣分。
    Pen(G)\mathrm{Pen}(G)

BIC评分详解

在众多评分函数中,贝叶斯信息准则(BIC) 是最常用的一种。它把复杂度惩罚设计得非常直白:每多一个自由参数,就扣掉 \(\frac{1}{2}\log m\) 分,其中 \(m\) 是样本量。样本越多,扣分越重,因为数据量充足时我们更应警惕复杂模型的自欺欺人。

BIC 的另一个重要性质是 可分解性(Decomposability):整个图的评分等于每个节点局部评分的和。每个节点的局部评分只取决于该节点及其父节点。这就好比评委可以分组试吃——每次只评审一道菜,不需要重做整桌宴席。搜索算法因此可以高效地局部更新分数:改动一条边时,只需重算受影响的局部评分。

BIC 的公式为:

BIC 在拟合与简洁之间取得了精妙的平衡。但“尺子”再好,也要有高效的“搜索策略”才能真正用起来——下一节,评委如何系统地试吃候选菜谱。

---

BIC(G∣D)=∑i=1n[log⁡P(Di∣PaG(Xi))−di2log⁡m]\mathrm{BIC}(G \mid D) = \sum_{i=1}^{n} \Big[ \log \mathbb{P}(D_i \mid \mathrm{Pa}_G(X_i)) - \frac{d_i}{2} \log m \Big]
BIC 总分等于每个节点的“局部对数似然”减去“局部参数量的复杂度惩罚”。每道菜独立打分,最后加总。
  1. 节点求和BIC 是各节点局部评分之和。好比各道菜分别打分后汇总为总分。
    ∑i=1n\sum_{i=1}^{n}
  2. 局部似然在给定父节点集合下,该节点数据的对数似然。与“在特定前置工序下,这道菜的表现”对应。
    log⁡P(Di∣PaG(Xi))\log \mathbb{P}(D_i \mid \mathrm{Pa}_G(X_i))
  3. 局部复杂度\(d_i\) 是该节点条件概率表的自由参数个数。参数越多,说明这道菜的工序越细碎,扣分越多。
    di2log⁡m\frac{d_i}{2} \log m

贪婪搜索算法

评委面临的问题是:候选菜谱实在太多,不可能逐一试吃。一个实用的策略是 爬山法(Hill Climbing):先随便拿一份菜谱作为起点,然后做小改动(加一条边、删一条边、翻转一条边的方向),如果改完总分更高,就接受这个改动并继续;如果分数没有提高,就换一个改动方向再试。就像做菜时每次都试放一点点新调料,好吃就保留,不好吃就换别的。

搜索过程的核心是三个“操作算子”:加边(Add)、减边(Delete)、翻转边(Reverse)。每次操作后,必须检查新结构仍是合法的 DAG(无环)。这好比做菜时调整工序顺序,但要确保不出现“先有鸡还是先有蛋”的逻辑死循环。得益于 BIC 的可分解性,每次操作只需重算被影响节点的局部分数,效率很高。

贪心策略并不能保证找到全局最优评分结构——它可能停在某个“局部最优”的山顶,而旁边还有更高的山峰。缓解方法是随机重启(从不同初始点重新爬山)或使用模拟退火等更复杂的策略。但从实际效果看,爬山法在大多数真实场景中已能给出不错的菜谱。

多次操作后,搜索将收敛到一个(局部)最优结构。这些努力获得的菜谱已经足够好——最后一步,我们要反思:倒推出来的工序,真的就是厨房里的真实流程吗?

---

Gt+1={G′if Score(G′∣D)>Score(Gt∣D)GtotherwiseG_{t+1} =\begin{cases}G' & \text{if } \mathrm{Score}(G' \mid D) > \mathrm{Score}(G_t \mid D) \\G_t & \text{otherwise}\end{cases}
只有分数更高的邻接结构才会被接受。评委只保留“更好吃且更精炼”的改良版菜谱。
  1. 邻接结构对当前结构做一次加边、减边或翻边操作后得到的新结构。
    G′G'
  2. 分数比较新结构分数高于当前结构时才接受,否则维持原状。
    Score(G′∣D)>Score(Gt∣D)\mathrm{Score}(G' \mid D) > \mathrm{Score}(G_t \mid D)

可识别性讨论

评委用评分法反推出了最合理的菜谱,但这份菜谱未必是厨房里的原版。因为不同的菜谱可能有完全相同的成品口味——不同的 DAG 结构可能在统计上不可区分。这种现象称为 马尔可夫等价类(Markov Equivalence Class):如果两张图拥有相同的骨架(不考虑方向的边集合)和相同的“V 型结构”(又称汇连结构),它们在数据上就不可区分,评分函数给它们的分数也相同。

等价类意味着:仅凭观测数据,我们无法确定一条边的方向到底是 A→B 还是 B→A。好比评委尝出“鸡蛋和番茄在一起很好吃”,却无法判断是先炒鸡蛋还是先炒番茄。这时就需要借助 结构先验 或 领域知识:比如老厨师(领域专家)说“番茄鸡蛋必须先炒鸡蛋,因为鸡蛋遇热定型更快”,我们就在搜索中给 A→B 加上额外加分。

更一般地,结构学习得到的网络描述的是变量间的条件依赖关系,而不等于因果关系。它更像一份“经评委认可、口味一致的菜谱”,而不是“记录厨房真实操作的手记”。要确认因果方向,还需借助干预实验或更严格的因果发现方法。

认识可识别性的边界,能让我们对算法输出的结构保持审慎:它是一份“口感最佳”的菜谱,而非“厨房实录”。带着这种审慎,才能在实践中做出可靠决策。

G1≡G2  ⟺  ∀D, Score(G1∣D)=Score(G2∣D)G_1 \equiv G_2 \iff \forall D,\ \mathrm{Score}(G_1 \mid D) = \mathrm{Score}(G_2 \mid D)
两张图等价当且仅当它们在所有数据下评分相同。评委无法靠口味区分这两份菜谱。
  1. 等价关系两张图在评分函数下不可区分,记为 \(G_1 \equiv G_2\)。
    G1≡G2G_1 \equiv G_2
  2. 全数据等分对任意数据,两张图的评分完全一致,说明数据本身不携带区分方向的信息。
    ∀D, Score(G1∣D)=Score(G2∣D)\forall D,\ \mathrm{Score}(G_1 \mid D) = \mathrm{Score}(G_2 \mid D)

AI 应用场景

医疗诊断辅助

阶段:结构学习 + 概率推理

解决的问题:传统医疗诊断依赖专家手工构建规则库,耗时且难以覆盖复杂共病场景。通过贝叶斯网络结构学习,可从电子病历数据中自动发现症状与疾病之间的依赖骨架,辅助医生定位关键风险因素。

具体案例:某三甲医院用 5 万份呼吸科病历做输入,学习“咳嗽—发热—肺部阴影—确诊肺炎”—类变量间的 DAG 结构。得到的网络准确识别出“发热”与“肺部阴影”之间有一条强依赖边,而“咳嗽”与“确诊”之间的直接依赖较弱,帮助医生优化了检查顺序。

银行风险建模

阶段:结构学习 + 模型解释

解决的问题:传统信用评分卡以逻辑回归为主,难以刻画变量间的非线性依赖与传导路径。贝叶斯网络评分法可自动构建“收入—负债比—逾期—违约”的依赖链,提升模型可解释性。

具体案例:某消金公司使用 BIC 分数 + 爬山法对 20 万条借贷记录进行结构学习,发现“负债比”是“逾期”的最强父节点,且“收入”仅通过影响“负债比”间接作用于“逾期”。业务团队据此调整了风控策略。

Python 演示

python108 行
import numpy as npfrom itertools import combinations class BayesNetLearner:    def __init__(self, data, names):        self.data = data        self.names = names        self.n = data.shape[1]        self.parents = {}        for i in range(self.n):            self.parents[i] = []     def local_bic(self, node, parents):        m = self.data.shape[0]        pa = list(parents)        if pa:            keys, counts = np.unique(                self.data[:, pa + [node]], axis=0, return_counts=True)            cond_counts = {}            for key, cnt in zip(keys, counts):                pa_key = tuple(key[:-1])                cond_counts.setdefault(pa_key, [0, 0])                cond_counts[pa_key][0] += cnt                cond_counts[pa_key][1] += cnt            n_params = len(cond_counts) * (len(np.unique(self.data[:, node])) - 1)            ll = 0.0            for key, cnt in zip(keys, counts):                pa_key = tuple(key[:-1])                p = cnt / cond_counts[pa_key][1]                ll += cnt * np.log(p)        else:            vals, counts = np.unique(self.data[:, node], return_counts=True)            probs = counts / m            ll = np.sum(counts * np.log(probs))            n_params = len(vals) - 1        return ll - 0.5 * n_params * np.log(m)     def total_bic(self):        return sum(self.local_bic(i, self.parents[i]) for i in range(self.n))     def has_cycle(self, parent_map, start):        visited = set()        stack = [(start, iter(parent_map.get(start, [])))]        while stack:            node, it = stack[-1]            advanced = False            for child in it:                if child == start:                    return True                if child not in visited:                    visited.add(child)                    stack.append((child, iter(parent_map.get(child, []))))                    advanced = True                    break            if not advanced:                stack.pop()        return False     def learn(self, max_iter=20):        for _ in range(max_iter):            improved = False            best_gain = 0            best_op = None            for i in range(self.n):                for candidate in range(self.n):                    if candidate == i or candidate in self.parents[i]:                        continue                    old_score = self.local_bic(i, self.parents[i])                    new_parents = self.parents[i] + [candidate]                    new_map = dict(self.parents)                    new_map[i] = new_parents                    if self.has_cycle(new_map, i):                        continue                    new_score = self.local_bic(i, new_parents)                    gain = new_score - old_score                    if gain > best_gain:                        best_gain = gain                        best_op = ('add', i, candidate)                for rem in list(self.parents[i]):                    old_score = self.local_bic(i, self.parents[i])                    new_parents = [p for p in self.parents[i] if p != rem]                    new_score = self.local_bic(i, new_parents)                    gain = new_score - old_score                    if gain > best_gain:                        best_gain = gain                        best_op = ('del', i, rem)            if best_op:                op, node, other = best_op                if op == 'add':                    self.parents[node].append(other)                else:                    self.parents[node].remove(other)                improved = True            if not improved:                break        return self.parents np.random.seed(42)m = 2000X = np.random.binomial(n=1, p=0.7, size=m)Y = np.array([np.random.binomial(n=1, p=0.8 if x else 0.3) for x in X])Z = np.array([np.random.binomial(n=1, p=0.9 if y else 0.2) for y in Y])data = np.column_stack([X, Y, Z])learner = BayesNetLearner(data, ['X', 'Y', 'Z'])result = learner.learn()for node, pars in result.items():    print(node, '->', pars)print('BIC:', round(learner.total_bic(), 2))
目的:实现基于 BIC 分数与爬山搜索的贝叶斯网络结构学习,演示如何从数据中恢复依赖骨架。
  1. np.unique(self.data[:, pa + [node]], axis=0, return_counts=True)按父节点与当前节点取值组合做计数,这是似然计算的“试吃样本统计”。
  2. ll += cnt * np.log(p)累计对数似然,每份数据都贡献自己的“口味满意度”。
  3. n_params = len(cond_counts) * (...)条件概率表的自由参数个数,决定复杂度惩罚力度。
  4. return ll - 0.5 * n_params * np.log(m)BIC 局部分数:拟合减惩罚,评委的综合打分表。
  5. self.has_cycle(new_map, i)检查加边后是否出现有向环,确保菜谱工序不出现死循环。
  6. gain > best_gain记录最大改进的操作,每次迭代只做一次最好的改动。
  7. learner.learn()爬山法主循环,直到没有操作能提高分数为止。

常见误区

误区我用 BIC 分数筛选出的网络结构,里面的箭头方向应该就代表真实的因果关系吧?既然评分最高,说明 A→B 就是 A 导致 B。

正解评分函数衡量的是“条件依赖关系”与数据的匹配度,而非因果机制。马尔可夫等价类告诉我们,A→B 与 B→A 在纯观测数据下可能完全不可分。好比评委能尝出番茄和鸡蛋搭配完美,却无法通过口味判断厨师是先炒番茄还是先炒鸡蛋。要确认因果方向,必须结合干预数据或领域知识。

为何常见:箭头天然带有“方向感”,人们容易把结构图中的方向性投射为因果性,忽略了统计等价类的存在。

误区复杂度惩罚会“扣分”,那我把惩罚系数设得很小,让模型只关注拟合度,分数不就更准确了吗?分数高说明结构一定更好。

正解惩罚项存在的意义是防止过拟合。去掉惩罚后,评分函数会偏好边数最多、参数最复杂的网络,把噪声也当作信号学进去,换一批数据表现急剧下降。惩罚项与似然项的平衡是评分方法的核心艺术:太轻则过拟合,太重则欠拟合。就像评委既要菜品好吃,也要菜谱简洁可复制,二者缺一不可。

为何常见:初学者直觉上认为“扣分”是坏事,而没有从正则化与模型选择的角度理解惩罚项的保护作用。

误区爬山法每一步都选择分数提升最大的改动,最终收敛的结构应该就是所有结构里分数最高的那个吧?

正解爬山法是贪心算法,只能保证收敛到局部最优。搜索空间存在大量“局部山峰”,从不同初始点出发可能到达不同的山顶。好比爬山时只向上走,最终到达的可能是附近的小山头,而不是整个山脉的最高峰。缓解方法包括随机重启、模拟退火或禁忌搜索。

为何常见:“每一步都变好”容易让人误以为“最终必然最好”,忽略了贪心策略的天然局限。

误区既然数据能自动学到结构,我只需要把数据扔给算法,不需要任何专家知识,得到的结果就是客观的。

正解纯粹数据驱动的结构学习存在等价类方向不确定、局部最优、样本噪声等问题。融入结构先验(例如对某些边赋先验概率)或在搜索后由专家修正边方向,能显著提升结构的可靠性。领域知识就像老厨师的经验之谈:评委尝不出先炒蛋还是先炒番茄时,他的经验能帮你定下方向。

为何常见:AI 的“自动”能力让初学者高估了纯数据驱动的能力,忽视了统计识别极限。

面试题

Q1. 贝叶斯网络的结构本质上是什么?

单选题 · easy

  • A.完全有向图且允许自环
  • B.有向无环图加上概率参数← 正确答案
  • C.无向图表达变量相关性
  • D.决策树和随机森林集成

答案:B。贝叶斯网络的核心是 DAG 表达变量依赖关系,每个节点附带条件概率表,二者结合定义联合分布。A 错在“允许自环”,C 错在“无向”,D 与贝叶斯网络无关。

Q2. BIC 评分中的惩罚项主要作用是什么?

单选题 · easy

  • A.提高数据拟合的精确度
  • B.惩罚复杂结构防止过拟合← 正确答案
  • C.加速搜索算法的收敛速度
  • D.将离散数据转化为连续数据

答案:B。BIC 中 \(-\frac{d_i}{2}\log m\) 项对参数数量 \(d_i\) 做惩罚,样本越多扣分越重,防止复杂模型把噪声当信号。它不直接提升拟合,反而对“过度拟合”做约束。

Q3. 若两张 DAG 属于同一马尔可夫等价类,则它们:

单选题 · mid

  • A.结构完全相同,方向完全一致
  • B.观测数据下评分总是相同← 正确答案
  • C.拥有不同的条件独立关系
  • D.一定具有相同的因果方向

答案:B。等价类的定义是骨架相同且 V 型结构相同,使得它们在任意观测数据下联合分布相同,评分函数不可区分。C 与定义矛盾,D 混淆了统计等价与因果识别。

Q4. 爬山法搜索结构时,下列哪项操作是合法的?

单选题 · mid

  • A.添加一条边但允许出现环
  • B.删除边后保留孤立节点
  • C.翻转边方向且保持 DAG 合法← 正确答案
  • D.同时翻转所有边的方向

答案:C。翻转边方向后若仍无环则合法。A 有环非法;B 的孤立节点本身可以存在,但删除边通常只改变局部结构;D 翻转所有边可能破坏无环性。

Q5. BIC 分数具有“可分解性”是指:

单选题 · mid

  • A.整体分数可分解为各节点局部分数之和← 正确答案
  • B.数据可分解为多个独立子集分别计算
  • C.每个节点的分数与父节点完全无关
  • D.分数可分解为训练集与测试集两部分

答案:A。BIC 对每个节点独立计算 \(\log \mathbb{P}(D_i|\mathrm{Pa}) - \frac{d_i}{2}\log m\) 后求和,改变一条边只需重算相关节点局部分数,提升了搜索效率。

Q6. 关于结构学习中“局部最优”问题的描述,正确的是:

单选题 · hard

  • A.爬山法保证找到全局最优 DAG
  • B.BIC 分数不存在局部极大值
  • C.爬山法可能停在局部最优结构← 正确答案
  • D.随机重启无法缓解局部最优问题

答案:C。贪心搜索只接受分数提升的改动,可能在某个局部山峰停下而非全局最高峰。随机重启从不同初始点出发可缓解此问题。B 错误,BIC 的离散搜索空间存在局部极值。

Q7. 哪种方式可以辅助确定边的方向?

多选题 · mid

  • A.加入结构先验知识给特定方向加分← 正确答案
  • B.咨询领域专家修正候选结构← 正确答案
  • C.增加数据量就能消除方向不确定性
  • D.使用干预实验数据进行验证← 正确答案

答案:ABD。结构先验与专家知识是等价类方向摇摆的常用修正手段,干预实验能突破观测数据的识别极限。增加数据量只能让参数估计更准,无法消除等价类造成的方向不可识别。

Q8. 贝叶斯网络的“联合分布分解”意味着:

多选题 · mid

  • A.联合概率是各条件概率的乘积← 正确答案
  • B.每个节点仅依赖其非后代节点← 正确答案
  • C.马尔可夫条件独立性自然成立← 正确答案
  • D.所有变量之间彼此完全独立

答案:ABC。联合概率分解为 \(\prod P(X_i|\mathrm{Pa}(X_i))\),隐含每个节点在给定父节点时独立于非后代节点,即马尔可夫性。D 与网络依赖语义矛盾。

Q9. BIC 中参数数 \(d_i\) 的含义是什么?

单选题 · hard

  • A.第 i 个节点的父节点数量
  • B.第 i 个节点的数据样本数量
  • C.第 i 个节点 CPT 的自由参数个数← 正确答案
  • D.第 i 个节点的取值状态总数

答案:C。\(d_i\) 是节点 i 条件概率表中自由参数的个数,由父节点取值组合数和节点状态数决定。它衡量该局部模型的复杂度,用于惩罚项中。

Q10. 基于分数结构学习的完整流程包括:

多选题 · hard

  • A.定义可分解的评分函数← 正确答案
  • B.设计高效的结构搜索策略← 正确答案
  • C.通过梯度下降优化边的方向
  • D.对输出结构做等价类分析← 正确答案

答案:ABD。标准流程是评分函数 + 搜索策略 + 结构校验/等价类分析。C 错误,DAG 结构搜索是离散组合优化,梯度下降不适用。