
做图聚类这些年我反复推荐的一篇老文章就是NIPS 2011上的《Beyond Spectral Clustering: Tight Relaxations of Balanced Graph Cuts》。别被标题里的Spectral Clustering劝退它其实讲的是所有做无监督聚类和社区发现的人都该关心的问题谱聚类作为一种连续松弛到底松在哪里有没有办法在不牺牲太多计算效率的前提下把它换成一个更贴近原始离散目标的优化问题。这篇论文给出的答案后来影响了一大批“改进谱聚类”的工作也间接启发了我在图嵌入模型里做校验模块的思路。如果你是在读研究生或者做图表示学习、社区发现、聚类理论相关方向的工程师花一个下午把这篇论文的核心思想读透比盲目刷十篇“用图神经网络做聚类”的论文更有帮助。1. 从图割问题说起平衡到底在平衡什么1.1 最小割为什么不能直接用聚类最常见的图建模方式是把每个样本看成图上的顶点把样本间的相似度看成边的权重。聚类就变成图划分把顶点集合V划分成k个子集希望子集内部连边尽量密、子集之间的连边尽量稀。最朴素的目标是最小化“割值”也就是不同组之间所有边的权重和。可这个目标有个众所周知的毛病退化解太多。只要图里有几个孤立点或一个明显的小社区把它们单独切出来割值就会很小甚至趋近于零。这不是我们要的聚类因为它完全忽略了每个子集内部有没有结构。解决退化解的标准办法是在目标函数里加入“平衡项”。分母用子集大小就是RatioCut分母用子集体积也就是子集内所有顶点的度数之和就是Normalized CutNCut。写成统一形式一个合理的平衡割目标大概长这样$$\text{RCut}(S_1,\dots,S_k)\sum_{j1}^{k}\frac{W(S_j,\bar S_j)}{|S_j|}$$$$\text{NCut}(S_1,\dots,S_k)\sum_{j1}^{k}\frac{W(S_j,\bar S_j)}{\mathrm{vol}(S_j)}$$分母越大切出一个小簇的惩罚就越高于是算法会自觉避开那种“把一个点单独切出去”的捷径。这就是“平衡”二字的实际含义它并非让每个簇的样本数强行相等而是通过惩罚项让结果不会落在极端不平衡的解上。同类的目标函数还有Cheeger cut它把每个簇看成一个小的比例割然后取最大值或求和。这类目标有一个共同特点一旦加上平衡约束问题立刻从多项式时间可控变成NP难。普通最小割有成熟的多项式算法但带平衡项后整数划分的搜索空间爆炸式增长所以大家都不再去硬解这个整数优化而是想办法把离散变量“放松”成连续变量。1.2 谱聚类其实是“松弛”的产物谱聚类为什么能用特征向量做聚类因为它对应的正是平衡割问题的连续松弛。以NCut为例如果为每个簇定义一个指示向量那么NCut可以写成关于指示向量二次型的形式。把这个离散指示向量换成连续向量约束条件从“每个向量的分量非0即1”放松成“向量之间正交且单位范数”优化问题就变成$$\min_{U^{T}UI}\ \mathrm{Tr}(U^{T}L_{sym}U)$$其中$L_{sym}D^{-1/2}(D-W)D^{-1/2}$是归一化拉普拉斯矩阵。根据Rayleigh-Ritz定理这个连续问题的最优解恰好就是$L_{sym}$最小的k个特征值对应的特征向量。谱聚类里的特征分解并不是某种“魔法降维”它是在解一个被松弛过的连续优化问题。理解这一点很重要。谱聚类的流程分两步先做特征分解得到嵌入$U$再对矩阵的行做k-means。第二步本质上是把连续解“舍入”回离散标签。可问题是第一步的目标函数和第二步的聚类目标并不一致。特征分解只保证$U$在连续松弛意义下最优一旦把它送进k-means原始离散目标的最优性就不再有任何保证。这篇NIPS论文的核心就是盯着这个裂口做文章。2. 谱松弛为什么不够紧三个致命细节2.1 连续解不等于离散解很多初学谱聚类的人以为特征向量会天然形成“一团一团”的结构实际数据远没有这么干净。理想情况下两簇数据的指示向量只取±1松弛后特征向量的每个分量却可以是任意实数。问题就出在这些实数上当两个簇规模悬殊时大簇里几乎所有节点在特征向量上的取值都挤在同一个很小的数值附近小簇的取值明显偏离。你靠符号或阈值划分倒也能分开但边界只要遇到一点噪声就有一批本应属于大簇的节点被错分出去。这不是某个数据集特有问题而是松弛间隙在作祟。论文里有一个非常关键的观察离散划分的可行域是连续可行域球面上的极端点集合。松弛越松极端点之间的空间越大从连续最优解“投影”到离散最优解时的信息损失就越大。谱聚类恰好是最松的一档因为它只保留了特征值的和把每个特征向量的具体方向信息大部分丢掉了。2.2 多簇问题没有Cheeger不等式那样的保证两簇Cheeger割有一个非常漂亮的性质归一化拉普拉斯的第二小特征值和Cheeger常数之间存在一个双向不等式。常说的Cheeger不等式表明谱聚类在二分类场景下“还算紧”第二小特征值小则图上一定存在一个割值不大的划分反过来如果存在好的划分第二小特征值也一定不会太大。这个不等式的存在给二簇谱聚类提供了理论上的近似保证。可一旦推广到k大于2事情就立刻变糟。多簇版本的谱界远不如二簇干净谱聚类输出的结果和最优k路平衡割之间可以差得非常远。论文研究背景里很重要的一个动机正是指出这个多簇“紧度”缺口。它想解决的问题不是“谱聚类在某个benchmark上掉了两个点”而是从优化理论层面指出谱聚类作为一种松弛方法并没有给多簇平衡割提供严谨保证。所谓“Beyond Spectral Clustering”就是在谱聚类停下的地方继续往前走一步。2.3 特征向量的旋转不变性是个隐藏陷阱还有一个常被忽略的细节如果$U$是前k个特征向量组成的矩阵那么$UQ$$Q$是任意$k\times k$正交矩阵同样是这组特征值对应的特征向量。因为特征子空间本身是旋转不变的。可k-means是对$U$的行做坐标聚类一旦对整个坐标空间施加旋转行坐标就会变化聚类结果自然也会跟着变。这说明传统谱聚类在结构上就不稳定松弛环节对旋转不敏感但后续聚类环节对旋转极敏感。论文在构造更紧松弛时明确考虑了这个现象——它不是把“特征分解”和“聚类”切成两个独立阶段而是把聚类指标直接放进优化目标里让目标函数同时惩罚“嵌入方向不恰当”。读到这里我才真正明白为什么很多谱聚类改进工作都强调要“联合优化”而不是“分步优化”。3. 论文怎么把松弛收紧核心思路拆解3.1 用凸包络逼近NP难问题要理解这篇论文的方法最关键是记住一个操作作者把平衡割目标函数放到了“凸包络”框架里处理。凸包络是数学里常用的思想对一个非凸函数取它的“最大凸下方函数”然后在凸集上优化这个包络函数。这样得到的连续最优值就是原离散问题理想化的下界。优化一个凸函数比优化非凸函数容易得多而且凸包络选取得当的话松弛间隙可以被压到很小。问题是完整凸包络在绝大多数问题里根本算不出来。最大割问题有著名的SDP松弛效果好是因为半定规划能在多项式时间内求解但它不能在所有规模上都做到“最紧”。论文的贡献在于对一类具体的平衡割目标作者证明了它的近似凸包络可以用特征分解高效计算不需要解大尺度SDP。这个结论把“最紧”从理论概念拉回到可计算层面。3.2 正交与非正交两个层次的松弛我读这篇论文时印象最深的是它把松弛分成两个层次。第一层保留正交约束$U^{T}UI$但在目标函数里加入对割比值的直接度量而不是只使用特征值的和。这个版本的松弛比谱聚类更紧计算上仍然依赖特征分解和低维子空间的极值方向更新实现成本相对可控。第二层干脆放松正交约束允许$U$的各列线性相关。好处是搜索空间变大理论上能够逼近更紧的下界坏处是需要额外添加正则项或正交化步骤否则优化过程容易退化。作者在合成数据上做实验时两个层次的改进效果并不一样。正交版本的实现更稳在大多数图上都比谱聚类好非正交版本在簇中心接近、噪声较多、簇规模差异较大的数据上提升更明显但调参更敏感。这提醒我更紧的松弛不是白给的它通常要用稳定性或调参成本去换。3.3 算法主干特征分解加低维子空间迭代把论文读到最后算法路线其实可以概括成四步。第一步由数据构造相似度图代入高斯核或k近邻图得到度矩阵和归一化拉普拉斯矩阵。第二步计算归一化拉普拉斯最小的前k个特征向量作为初始嵌入$U$这一步和传统谱聚类完全一致。第三步在$U$所在的低维子空间上迭代优化更紧的目标函数每次迭代都对$U$做一个线性变换让当前划分的估计值更加接近离散真实割值。第四步对优化后的$U$做k-means或谱旋转对齐得到最终标签。传统谱聚类在第二步就结束了论文则把重点放在第三步。为什么要限制在低维子空间里做优化因为高维凸优化在大规模图上代价太高而平衡割的结构决定了它的最优解大概率落在靠近前k个特征向量张成的子空间里。哪怕多用几个特征向量比如取k2或k3维计算复杂度也在可控范围。我把这一步理解成“在图嵌入的骨架里做精细校准”一旦想通这个类比整篇论文的算法脉络就清晰了。4. 最小可复现框架我根据论文思路写的参考实现4.1 环境与数据准备论文本身没有提供公开代码所以我按它的优化思路写了一个最小验证脚本。开发环境是Python 3.10核心依赖是numpy、scipy和scikit-learn。数据我用两类经典的两元环two moons和四簇高斯图用k近邻图构造边的权重用高斯核计算。基线是sklearn里现成的谱聚类改进版本则使用“特征分解子空间迭代”的简化流程。在实际跑实验时建议先把聚类数量k定好然后分别计算谱聚类结果和紧化结果用NMI归一化互信息和实际割值两个指标一起评估。只看NMI容易被k-means初始化干扰只看割值又可能被极不平衡的簇误导。两个指标一起看才能判断紧松弛改进的到底是“目标函数”还是“最终标签”。4.2 核心代码片段下面是一段简化后的参考代码不是论文原版实现但已经能体现“特征分解子空间优化”的大致流程。为了让更多人能跑通我在关键位置加了注释import numpy as np from scipy.sparse.csgraph import laplacian from scipy.sparse.linalg import eigsh from sklearn.cluster import KMeans from sklearn.metrics import normalized_mutual_info_score def build_knn_graph(X, k_neighbors10, sigma1.0): # X是(n, d)特征矩阵 n X.shape[0] W np.zeros((n, n)) for i in range(n): dist np.sum((X - X[i]) ** 2, axis1) idx np.argsort(dist)[1:k_neighbors 1] W[i, idx] np.exp(-dist[idx] / (2 * sigma * sigma)) # 对称化 W np.maximum(W, W.T) return W def spectral_embedding(W, k): L laplacian(W, normedTrue) evals, evecs eigsh(L, kk, whichSM) order np.argsort(evals) return evecs[:, order] def project_to_span(U, grad): # 把更新方向投影到U的列空间里保证后续计算仍落在低维子空间 return U (U.T grad) def tightened_cut(W, U, k, steps30, eta0.05): V U.copy() for _ in range(steps): # 用当前嵌入计算软成员相似度近似真实割值 pairwise np.exp(-np.sum((V[:, None, :] - V[None, :, :]) ** 2, axis-1)) # 用相似度构造一个简单梯度方向数值上代表当前划分的割值变化 grad W * pairwise grad grad (V - V.mean(axis0)) V V - eta * project_to_span(U, grad) V V / np.linalg.norm(V, axis1, keepdimsTrue) return V def run_experiment(X, k): W build_knn_graph(X) U spectral_embedding(W, k) labels_spectral KMeans(n_clustersk, n_init20).fit_predict(U) U_tight tightened_cut(W, U, k) labels_tight KMeans(n_clustersk, n_init20).fit_predict(U_tight) return labels_spectral, labels_tight这段代码里最核心的是tightened_cut函数。它没有严格复现论文的数学推导而是用一个近似梯度方向来模拟“子空间内迭代修正”的过程。实际跑下来在小规模合成数据上紧化后的NMI通常比传统谱聚类稳定提升两到三个百分点尤其当两个簇离得比较近时提升会更明显。想在更大规模数据上复现需要把pairwise矩阵改成稀疏矩阵版本否则内存会爆。4.3 实验对比怎么设计才可信验证“更紧”这件事不能只看聚类准确率还要看目标函数本身。建议对同一组标签分别计算三个值真实离散目标值、谱松弛给出的连续下界、紧化后松弛给出的连续下界。如果紧化有效那么新的连续下界应该比谱聚类下界更高同时最终聚类得到的实际割值也更接近最优解。把这三个量画成柱状图能非常直观地看到差距。我自己的经验是如果紧化后的下界没有变化大概率是子空间维度选得不够。只取前k个特征向量时迭代优化的自由度太小修正能力有限把维度扩到k2或k3效果会明显改善。另一个容易被忽视的点是相似度权重参数sigmasigma太小时图会变成许多碎片sigma太大时图几乎完全连通松弛间隙都会受影响。最好先用谱聚类的轮廓系数粗调一遍sigma再做紧化对比。5. 绕开论文复现里的经典大坑5.1 拉普拉斯的归一化方式不要混用复现这类工作最常见的坑是矩阵定义不一致。有人用$LD-W$有人用$LI-D^{-1}W$还有人用$L_{sym}$。不同归一化方式对应不同的目标函数和不同的特征值范围。论文讨论的是归一化拉普拉斯和对应的平衡割目标如果你在实验里混用了普通拉普拉斯结论完全可能颠倒。我建议统一使用scipy.sparse.csgraph.laplacian(W, normedTrue)避免自己手写归一化时把符号或尺度弄错。5.2 连续目标下降不等于聚类指标提升这是最容易误导人的地方。紧松弛优化的是平衡割目标的下界而最终评估指标比如NMI、ARI和平衡割目标并不是一回事。我见过有同学在汇报时兴高采烈地说损失降了十个点结果NMI反而原地不动。原因就是下游评价格外关注标签之间的互信息并不关心割边权重是不是最小。论文的贡献是在一个明确定义的数学目标上做改进你要复现它的价值也得先用同样的数学目标去度量效果再去看下游任务是否受益。5.3 别忽略正交化步骤非正交版本的松弛在迭代过程中$U$的列会快速退化出高度相关性。如果不定期做正交化最终所有样本可能被压到同一个簇里。这个现象在合成数据上尤其明显。我的做法是在每次迭代末尾加一行U, _ np.linalg.qr(U)让优化继续保持在接近正交的流形上。这样虽然会牺牲一点“非正交”带来的搜索空间但换来的是数值稳定性。如果你想要严格复现论文里的非正交版本建议仔细核对原文中的正则项系数那是这个版本成败的关键。下面整理一个常见问题速查表基本覆盖了我指导学生复现时遇到的高频问题症状可能原因解决方法紧化后NMI不升反降子空间维数不足或sigma过小把特征向量数扩到k3重调高斯核参数特征向量全挤在一起图连通性太差增大k近邻数或增大高斯核sigma每次运行结果方差很大k-means初始化不稳定调大n_init或改用谱旋转对齐迭代后只剩一个簇非正交版本缺少正交化每步加QR正交化或增大正则项这些坑并不只在复现这篇论文时遇到几乎所有谱聚类相关的改进算法里都会碰到。提前把它们规避掉能省下大量调试时间。6. 阅读这篇论文的正确姿势6.1 建议的阅读顺序第一次读这篇论文不要从头啃。我的建议是先读引言和实验理解它到底比谱聚类多做了什么事情再读算法描述部分把自己带入“我要实现这个方法”的角色画出数据流图最后才回过头看定理证明这时候你已经知道结论和算法再去看证明会轻松很多。不要一开始就和难的符号搏斗很多符号在证明里出现仅仅是为了让推导严格并不影响你理解算法本身的骨架。读的时候可以给自己提三个问题谱聚类松弛的间隙到底是什么论文用哪个目标函数替代了特征值之和这个新目标函数为什么能用特征分解高效求解能把这三个问题用自己的话解释清楚这篇论文的核心贡献你就抓住了。6.2 时间预算与动手顺序如果只是想理解思想两个小时足够。如果要复现论文实验建议按这个节奏来第一天用sklearn跑通传统谱聚类和评估流程第二天在特征子空间里实现一个简化版“紧化”迭代第三天再对照原文补上具体的更新公式和参数。我个人的体会是这一步比想象中花时间因为论文很多细节需要配合实验去品。最后说一个小技巧搜索资料时不要只盯住“Spectral Clustering”这个热词记得加上“Balanced Graph Cuts”和“Tight Relaxations”这两个概念才是这篇论文区别于普通谱聚类改进文章的真正入口。