☰
缺失视图下的多视图聚类:NMF共享表示与Numpy复现笔记
2026/10/15 13:21:54 网站建设 项目流程

最近在复盘一个多传感器工业场景分类项目时,又撞上了那个老问题:同一个工件有X光图像、红外热图和工艺文本三种描述方式,但总有一部分样本因为传感器离线、传输丢包,导致某个视图整片缺失。数据量本来就紧张,删掉这些样本太可惜,简单按零填充又不靠谱。

这种“多视图学习中存在缺失视图”的场景,正是这篇论文笔记要解决的核心问题。论文标题很直白:Multi-view Learning with Incomplete Views,一句话概括它的核心思想就是把缺失位置当作权重0写进优化目标,用非负矩阵分解同时学出一个所有视图共享的共识表示,然后用这个表示做聚类或者分类。

我花了两天时间把论文的推导过程重新走了一遍,又用Numpy写了个简易复现,过程中踩了不少坑。这篇笔记会从问题背景、方法思路、推导细节、复现要点和避坑经验五个方面完整记录下来,适合正在做多视图聚类、多模态学习,或者想快速看懂NMF类工作的人参考。

1. 读前功课:多视图学习里“视图缺失”到底难在哪

1.1 先搞懂多视图学习在解决什么问题

多视图学习的设定说起来很简单:同一个对象,从不同角度采集信息,就得到多个视图。比如一条新闻,有文字正文、配图、用户的标签;一段视频,有画面帧、音频轨、字幕;一个工业产品,有视觉图像、振动信号、温度曲线。每个视图的特征维度、数据分布完全不一样,但都指向同一个底层对象。

在数学上,假设有N个样本,每个样本有M个视图,第v个视图用矩阵 \(X^{(v)} \in R^{d_v \times N}\) 表示,其中 \(d_v\) 是第v个视图的特征维度。多视图学习的目标就是利用这些不同视图的信息,让聚类、分类、检索这些下游任务做得比只用单一视图更好。

多视图能起作用靠的是两个基本假设。一是共识性:多个视图对同一个样本的描述在大方向上是统一的,所以存在一个共享的底层表示。二是互补性:每个视图都携带了其他视图没有的信息,用全了才能还原完整图景。这两者缺一不可,只强调共识会损失细节,只强调互补又难以对齐。

1.2 缺视图为什么是工程中的常态而不是例外

研究论文里经常画一个干净整齐的矩阵,每个视图都完整无缺。但工程项目里的真实数据根本不会那么听话。传感器故障、网络抖动、采集设备批次差异、标注人员遗漏,随便一个原因都会让某个样本缺少某个视图。

我自己的项目里就出现过这样的情况:一条产线连续采集了一周的样本,结果红外传感器的缓存写入偶尔失败,留下大概15%的样本只有X光图和工艺文本。去掉这些样本,训练集会缩水;用整批均值填补,又会把分布搞得面目全非。

缺视图问题本质上破坏了多视图学习算法的输入前提。大部分经典方法,比如多核学习、典型相关分析、共享子空间学习,都要求所有样本在所有视图上完整。一个样本缺了某个视图,就没法参与核矩阵计算,没法做跨视图相关性分析,也没法拼成一个完整的特征向量。

1.3 这篇论文想绕开的那个坑:数据修复

面对缺失视图,最直觉的解决方案是“先把缺的东西补回来”。补法无非三种:删除缺失样本、特征均值填充、用矩阵补全或者生成模型去预测缺失内容。

这三种方法各有问题。删除样本在缺失率高时等于自杀,数据越缺越不敢删。均值填充看上去简单,但会严重低估特征方差,给后续聚类引入系统性偏差。矩阵补全和生成模型听起来高级,但模型本身的训练依赖完整的视图关系,缺失一多,补出来的东西可能完全是模型幻觉,带了它的偏见过拟合进下游任务。

论文的做法是换一个思路:不补数据,而是在优化目标里让缺失位置“自动消失”。只要把缺失项权重设成0,它们就不参与误差计算和梯度更新,整个算法照样能跑,而且用的是纯粹的观察数据,没引入任何猜测值。这种思路的优雅之处在于它把数据修复问题转化成了优化建模问题。

2. 整体思路拆解:不补数据,把缺失写进损失函数

2.1 所有视图共用一个共识表示,各自保留专属映射

论文采用的核心工具是非负矩阵分解,简称NMF。NMF做的事情是把一个非负矩阵近似分解成两个低秩非负矩阵的乘积,通常写成 \(X \approx UH\),其中U可以理解为字典,H是样本在新表示下的编码。

在多视图场景下,自然延伸出一个设计:每个视图都有自己的字典矩阵 \(U^{(v)}\),但所有视图共享同一个编码矩阵P。用公式写就是:

[ X^{(v)} \approx U^{(v)} P ]

P对所有视图都是一样的,这就保证了跨视图的共识性。每个视图的H,也就是P,可以理解为样本在公共语义空间中的位置;而 \(U^{(v)}\) 负责把第v个视图特有的特征翻译到这个公共空间里。

这个设计很像一个多语言翻译团队。每个视图的字典U是各个专家的“个人语言习惯”,而P是大家共同要描述的那件事。每个专家用自己的语言写同一件事,最后把大家的描述对齐在一起,就能得到比单个专家更完整的信息。

2.2 0/1权重矩阵是这样把缺失“藏”进优化的

如何处理缺失视图,论文给了一个非常朴素的方案:为每个视图引入一个跟 \(X^{(v)}\) 同形状的0/1权重矩阵 \(W^{(v)}\)。对于第v个视图中存在的特征项,权重为1;缺失的特征项,权重为0。

然后把损失函数改写成带权重的形式:

[ \min_{U^{(v)}, P \ge 0} \sum_{v=1}^{M} \left| W^{(v)} \odot (X^{(v)} - U^{(v)} P) \right|_F^2 ]

其中 \(\odot\) 表示逐元素相乘。权重为0的位置,误差自动变成0,梯度自然也是0,所以缺失位置完全不参与参数更新。这个处理把缺失的随机性完全抽象掉了,不管缺失发生在哪个样本的哪些维度,只要把对应的权重置0,优化过程就自动跳过。

这种做法的另一个巨大好处是:不需要区分“样本缺失整个视图”还是“样本在某个视图中缺失部分维度”,统一用权重矩阵表达。实际工程里两种缺失形态都很常见,论文用一个机制同时覆盖了它们。

2.3 一句话版本的整体目标函数

把前面两个设计合在一起,方法的核心就是:对每个视图做带权重的NMF分解,视图之间通过共享共识矩阵P耦合,同时要求P非负,这样它天然适合作为聚类表示。

用一句话总结我的理解:每个视图用自己的字典去拟合一个共享的共识矩阵,缺失位置在拟合时自动跳过,最后得到的共识矩阵就是多视图聚类的特征表示。

这个方法之所以值得推荐,是因为它不需要复杂的数据补全模块,不需要求逆矩阵,整个算法的核心就是一个乘法更新规则,实现成本极低。而且由于权重矩阵只参与平均值计算,就算缺失模式不规则,也能自然处理。

3. 核心推导与更新规则:从目标函数到可运行代码

3.1 符号约定:先把自己的坐标轴统一起来

复现论文第一步就是统一符号,不然推导过程中维度对不上会非常痛苦。我采用的约定如下,列成表格方便对照:

符号含义维度
\(X^{(v)}\)第v个视图的特征矩阵\(d_v \times N\)
\(W^{(v)}\)第v个视图的0/1缺失权重矩阵\(d_v \times N\)
\(U^{(v)}\)第v个视图的字典矩阵\(d_v \times K\)
\(P\)所有视图共享的共识表示矩阵\(K \times N\)
N样本数量标量
\(d_v\)第v个视图的特征维度标量
K潜在表示维度,一般取聚类类别数标量

这里需要注意,有些论文习惯把样本放在行方向,P写成 \(N \times K\)。我选择了列是样本的约定,因为这跟常见的NMF矩阵分解符号一致,写代码时逐个视图对齐也方便。复现之前先固定好这个约定,能省掉大量调试维度的冤枉时间。

3.2 目标函数:加权重构误差加非负约束

完整的目标函数拆开来看其实只有两块。第一块是M个视图的加权重构误差之和,也就是前面公式里的F范数平方。第二块是约束条件,要求所有 \(U^{(v)}\) 和P都是非负矩阵。

NMF之所以要非负,一方面是因为很多原始特征本身就是非负的,比如图像的像素值、文本的词频,分解出来的U和P也自然希望非负,这样才能保留“部分构成整体”的可解释性。另一方面,非负约束配合乘法更新规则,把优化问题变成了一个没有负系数的叠加过程,数学性质好,迭代不容易震荡。

论文没有画蛇添足地加很多额外的正则项,比如稀疏约束、图拉普拉斯约束之类。这一点我很欣赏,因为多视图算法最大的坑就是超参数太多,每个视图来几个权重,再来几个正则系数,调参调到怀疑人生。论文把复杂度控制在最低,只保留一个K需要用户决定。

3.3 乘法更新规则是怎么来的

求解这个带非负约束的优化问题,主流方案就是乘法更新规则,这也是NMF最经典的求解方法。推导思路大致是:固定其他变量,对目标函数关于当前变量求梯度,结合KKT条件让“梯度乘以变量等于0”,整理之后得到变量自身的迭代式。

由于U和P是分块交替更新的,推导可以分解成两部分。更新 \(U^{(v)}\) 时,固定P和其他视图的U,只对这个视图求梯度;更新P时,需要把所有视图的梯度加起来,因为P出现在每个视图的误差项里。

关键技巧在于带权重的逐元素乘法。因为 \(W^{(v)}\) 只含0和1,损失函数里的 \((W \odot (X - UP))\) 可以展开成 \(W \odot X - W \odot (UP)\),这样一来,缺失权重就自然地进入分子和分母,形成一个和完整NMF形态几乎一致的更新规则。这就是论文最核心的观察:任何完整视图的NMF更新规则,只要把X换成 \(W \odot X\),把UP换成 \(W \odot (UP)\),就能直接处理缺失视图。

更新U的规则是:

[ U^{(v)} \leftarrow U^{(v)} \odot \frac{(W^{(v)} \odot X^{(v)}) P^T}{(W^{(v)} \odot (U^{(v)} P)) P^T + \epsilon} ]

更新共享P的规则是:

[ P \leftarrow P \odot \frac{\sum_{v=1}^{M} (U^{(v)})^T (W^{(v)} \odot X^{(v)})}{\sum_{v=1}^{M} (U^{(v)})^T (W^{(v)} \odot (U^{(v)} P)) + \epsilon} ]

其中 \(\epsilon\) 是防止除零的小常数,一般取 \(10^{-9}\) 级别。这个更新规则形式上非常简单,但每一步都有明确的优化意义:分子是“当前重构方向上的正梯度能量”,分母是“当前重构方向上的总量”,两者相除得到乘法系数。

3.4 从P到聚类结果:最后一步的细节

训练收敛之后,P就是所有样本在K维公共空间上的表示。这个矩阵的每一行对应一个潜在语义方向,每一列对应一个样本在该方向上的载荷。

拿到P之后,获取聚类结果有两种常见做法。第一种是把P直接当特征矩阵,对每一列跑一次k-means,得到K个簇。第二种因为P本身是NMF编码,它的每个维度天然带有“这个样本属于该语义类”的倾向,可以对每一列取最大值的下标作为聚类标签。

我自己的实验里,两种做法在大部分数据上结果很接近。取最大值的做法更轻量,不需要额外调k-means的随机种子;但在P的各个维度区分度不够的时候,跑k-means会更稳定一些。需要提一句的是,P训练完之后列方向可能有一些尺度差异,对P做一次按列的L2归一化再跑k-means,通常能得到更干净的聚类结果。

4. 复现实操:我把论文变成100行Numpy代码的过程

4.1 数据预处理:非负约束要先解决

复现的第一步不是写更新公式,而是处理数据。NMF要求输入矩阵非负,但真实数据很少自带这个性质。

最简单的处理方案是做一次线性平移:把每个视图的特征矩阵整体减去该视图的最小值,让所有数值变成非负。如果特征里存在极端离群值,平移之后再用L2范数按列归一化,也就是每个特征向量除以它的长度,这对收敛稳定性有明显帮助。

我踩过的一个坑是把几个视图放在一起做全局归一化。不同视图的特征量纲差异巨大,图像特征可能都在0到1之间,文本的词频可能到几百,工艺特征可能有负数。全局归一化会让量纲小的视图在损失函数里“失声”,导致P被大数值视图带偏。正确做法是按视图分开归一化,让每个视图在目标函数里的贡献大致可比。

4.2 缺失矩阵构造:随机挖洞也有讲究

做过缺失场景实验的人都知道,构造缺失矩阵不是简单用随机数生成器随便置0就完事。首先要区分两种缺失模式:随机缺失和结构化缺失。随机缺失是每个样本的每个特征独立地以一定概率缺失;结构化缺失是某些传感器在整个时间段内整体失效,导致一批样本同时缺失整个视图。

论文的权重矩阵机制对两种模式都成立,但实验验证时要分别构造。我建议先在较小缺失率下验证方法成立,比如10%到20%的随机缺失,再逐步提高缺失率。我实测下来,达到30%以上的缺失率时,性能会明显衰减,这时候可以尝试调整不同视图的权重来补救。

构造权重矩阵时需要记住一个原则:测试阶段的聚类评估必须只在有真实标签的完整样本上进行,而训练和验证阶段的缺失是人为构造的。如果我们把缺失事件本身当作真实标签的一部分,评估结果就会失真。

4.3 初始化、迭代次数与视图权重怎么定

NMF对初始化比较敏感,不同的随机种子可能收敛到不同的局部最优。我用的初始化方法是:所有U和P的元素从0到1的均匀分布中取随机数,不要取0,因为非负乘法更新里如果某个元素恰好是0,梯度信息可能一直无法传递给它,造成“死神经元”现象。

潜在维度K一般直接取聚类类别数。但论文讨论的是聚类任务,K可以无脑设为类别数;如果下游任务是分类,K可能要比类别数略大一些,给模型一点余量。

迭代次数方面,论文原始实验设置我没有专门去复现,但我自己的经验是200次迭代基本足够让目标函数收敛。可以在每20次迭代时输出一次加权重构误差,观察下降曲线趋于水平后就可以停。视图之间的默认权重相等即可,如果发现某个视图缺失率特别高,可以适当调低它的权重,避免大量缺失位置带来的噪声主导梯度。

4.4 核心代码片段与收敛观察

下面是我整理过的最小可运行代码片段,只包含核心更新逻辑,用Numpy实现。这个版本去掉了所有预处理和评估逻辑,方便看清论文算法的骨架。

import numpy as np def update_mvnmf(X_list, W_list, K, max_iter=300, seed=0): rng = np.random.RandomState(seed) M = len(X_list) N = X_list[0].shape[1] U_list = [rng.rand(X.shape[0], K) + 1e-3 for X in X_list] P = rng.rand(K, N) + 1e-3 eps = 1e-9 for it in range(max_iter): # 固定P,逐个更新U for v in range(M): Xv, Wv, Uv = X_list[v], W_list[v], U_list[v] WX = Wv * Xv WUP = Wv * (Uv @ P) Uv = Uv * ((WX @ P.T) / (WUP @ P.T + eps)) U_list[v] = Uv # 固定所有U,更新公共P numerator = sum(U_list[v].T @ (W_list[v] * X_list[v]) for v in range(M)) denominator = sum(U_list[v].T @ (W_list[v] * (U_list[v] @ P)) for v in range(M)) + eps P = P * (numerator / denominator) return U_list, P

我在地球物理和图像文本混合的模拟数据上跑过这段代码,观察到的现象是:前50次迭代重构损失下降很快,之后进入缓慢下降阶段,150次以后基本稳定。乘法更新规则的收敛轨迹比梯度下降平滑很多,不会出现损失突然上升的情况,这是因为每一步的乘法系数保证了目标函数的非增性质。

5. 复现踩坑与常见问题排查实录

5.1 分子分母齐刷刷变成0:数值稳定性

第一次跑通代码后,我遇到了一个很奇怪的现象:某些迭代步之后,P矩阵开始出现大量0,然后损失函数不再下降。排查后发现是数值下溢。当U的某些元素更新后被推到非常接近0,下一轮分母中对应项也会趋于0,分子也可能因为乘了微小值变成0,最终0除以0得到NaN或者0。

解决办法有三层。第一层是在分子分母里加 \(\epsilon\),我取的是 \(10^{-9}\),但更稳妥的取值是 \(10^{-8}\) 到 \(10^{-10}\) 之间,视数据量级而定。第二层是每次更新后做一次clip操作,把小于 \(10^{-10}\) 的值截断到 \(10^{-10}\),防止“死神经元”。第三层是初始化时加一个小偏移,避免U和P里有精确的0。

5.2 把W的维度搞错的尴尬

权重矩阵W的形状必须跟对应的X完全一致,也就是 \(d_v \times N\),而不是 \(N \times d_v\),更不是 \(N \times 1\) 的样本缺失标记向量。

我第一次实现时想当然地认为只需要记录每个样本是否缺失整个视图,于是构造了一个 \(N \times 1\) 的0/1向量,然后试图在Numpy广播里让它自动生效,结果更新方向完全错了,损失函数根本不下降。后来细看推导才意识到,权重需要在逐元素乘法中跟特征矩阵逐位置对齐,所以W和X的形状必须一样。

实际使用中,即使一个样本在某个视图中只有部分维度缺失,也完全可以用0/1权重标记到特征粒度。这么做不会增加算法复杂度,但能多保留一部分信息。

5.3 特征不是非负数据怎么办

如果数据里有强烈的负值,比如中心化后的特征、差分特征,直接套NMF会很别扭。我的处理流程是:第一优先做平移,让最小值为0;如果平移后数据特性被破坏,比如稀疏度改变,可以尝试绝对值变换,但这样会丢失正负符号信息。

还有一个可行方案是先用其他方法把特征映射到非负空间,比如对每个维度做min-max归一化到 \([0,1]\)。这本质上也是一种信息压缩,但它能让NMF的框架顺利工作。论文方法本身并不限制输入一定是图像灰度或词频,任何能转换成非负矩阵的数据都能用。

5.4 效果不及预期时,我按什么顺序排查

复现论文方法后效果不好,我建议按下面的顺序排查,而不是直接怀疑算法有问题。

第一步检查预处理。是不是有视图的量纲没有归一化,导致它在目标函数里被淹没。第二步检查缺失权重矩阵。随机生成的W是否真的覆盖了正确的缺失位置,样本维度顺序是否跟X一致。第三步检查目标函数是否单调下降。如果损失不降,问题几乎肯定在更新规则实现上,比如某个视图的W没有乘进去。第四步检查P的质量。把P可视化为二维嵌入,如果完全分不开,再考虑增加迭代次数或者换初始化种子。

我自己的经验是,论文方法在10%缺失率下几乎能追平完整数据版本,在30%缺失率下会掉一些性能,但比删除样本的方法好很多。如果连这个趋势都没看到,优先级最高的是回头检查第四步之前的代码问题。

6. 论文影响与我的独立判断

6.1 这篇论文为什么能成为经常被引的baseline

这篇论文在学界的影响力主要来自它的简单和干净。它没有引入复杂的生成模型,没有依赖深度神经网络,只用一个带权重的NMF更新规则就解决了缺失视图下的多视图聚类问题。正因为简洁,它很容易被复现,很多人拿它当比较的baseline。

从后续工作的视角看,这种“把缺失编成权重、集成进目标函数”的思路已经被广泛继承。很多多视图聚类、多模态弱监督方法都沿用了这一框架:在损失函数里对缺失位置进行屏蔽,而不是显式修补。可以说,这篇论文提供了一个非常稳定的基准,任何一个新的多视图缺失处理方法都绕不开跟它的对比。

6.2 局限性与可改进的空间

方法虽然优雅,但局限性也相当明显。首先是线性假设,NMF本质上只能捕捉视图之间的线性关系,遇到高度非线性的多模态数据,表达能力会比较有限。其次是非负要求,前面已经说过,需要做数据变换,这本身就会损失一些信息。第三是K需要提前确定,真实场景里聚类数往往不是预先知道的。第四,如果一个样本在所有视图中都缺失,那它在P中没有任何约束,训练出来的表示完全取决于初始化,论文方法对这种极端情况无能为力。

后续我见过一些改进方向:引入深度网络替换线性NMF,对K做自动选择,或者对P增加稀疏和正交约束来提升可辨识度。这些改进各有取舍,但并没有动摇“缺失位置在损失函数中屏蔽”这个核心观察的有效性。

6.3 一个“用损失函数拆论文”的通用方法

最后分享一个我自己的读论文习惯:遇到任何优化类方法,先试着把它还原成“一个损失函数加一个更新规则”,再思考如果我自己设计会怎么做。这篇论文的价值对我而言不只是给了一个公式,而是示范了如何把数据质量问题转化成优化问题。

从这个方法论出发,我后来在处理点击率预估的缺失特征、多传感器信号融合的断流数据时,都尝试优先设计权重矩阵,而不是急着调到补全模型。很多时候,一个精心设计的损失函数比一个复杂的生成模型更可靠,也更可控。这篇论文就是这个判断的典型例证。

复现这篇论文的那两天,最大的收获不是我记住了乘法更新规则,而是彻底理解了“缺什么不补什么,让优化过程绕开缺失位置”这个思路。如果你也要处理带缺失视图的数据,我建议不要一上来就上深度生成模型,先试试这个一百行代码就能跑通的NMF方法。它的表现不一定是最强的,但作为基线,它能帮你快速判断问题到底难在哪里,后续再决定要不要换更复杂的模型。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询