
标签不够用怎么办这是我做了一堆线性判别分析LDA落地项目之后碰到的真问题。LDA这方法本身不复杂有标签样本够了效果很稳但现实任务里往往标注成本高每个类别就十几个、甚至几个带标签样本这时候LDA的散度矩阵估计得歪歪扭扭投影方向一塌糊涂。半监督判别分析SDA就是拿来治这个病的——它在LDA的框架里把大量无标签样本也拉进来用它们的分布几何结构去约束投影方向。这篇我把SDA的原理从头拆到尾附带一份可以直接跑的MATLAB实现以及我调参踩过的坑适合正在做降维、特征提取或者被“标签太少”折磨的同学参考。1. 绕不开的问题当标签不够时LDA为什么失灵1.1 LDA的本质与“小样本困境”先快速过一遍LDA在干嘛。LDA的基本假设是数据存在一个线性投影方向使得投影之后类间距离尽量大、类内散布尽量小。用Fisher准则表述就是最大化J(w) (w^T S_b w) / (w^T S_w w)其中S_b是类间散度矩阵S_w是类内散度矩阵。这个优化问题的解就是广义特征值问题 S_b w λ S_w w 的最大特征值对应的特征向量。问题出在S_w和S_b的估计上。这两个矩阵完全由有标签样本计算S_w需要每个类别的均值向量和每个样本到类中心的差S_b需要各类均值相对全局均值的差。当标签样本少的时候估计出的类均值方差很大。假设一个类别只有3个标签样本这3个点在真实类中心附近随机晃动算出来的类中心偏差可能相当大。由这些不稳定均值构造的S_w和S_b求出来的投影方向当然也不可靠。这就是所谓“小样本困境”在判别分析里的具体表现。1.2 SDA的破局思路让无标签数据也参与建模SDA的核心思路很直接既然有标签样本太少那我把没标签的样本也拖进来一起用。但无标签样本没有类别信息不能直接贡献给S_w和S_b那它们能干什么答案是用它们描述数据分布的整体几何结构。一个非常朴素的观察是数据如果落在某个低维流形上那么近邻的样本应该具有相似的语义投影到低维空间后也应该彼此靠近。这个性质不需要任何标签就能从无标签样本上学到。SDA的做法是在LDA的分母上追加一个“流形正则项”把拉普拉斯特征映射Laplacian Eigenmaps里的局部保持项塞进目标函数。于是LDA的准则被改写成max_w (w^T S_b w) / (w^T (S_t β X^T L X) w)这里S_t是总散度矩阵L是依据全部样本含无标签构造的拉普拉斯矩阵β是正则化系数。加上这一项之后投影方向不仅要把带标签的类分开还要保证样本点之间的近邻关系不被破坏。即使标签很少只要无标签样本足够多、分布描述得准投影方向的“骨架”就不会走偏。这个思路用生活场景类比就是你手里只有几张不同班级的合影想让新来的转学生判断该进哪个班——光看这几张合影你顶多猜个大概但如果把整个年级所有学生在操场上的站位散点图都摆出来哪怕大多数人没贴名字你也能看出人群自然分成了几坨。SDA干的就是这事。2. SDA目标函数里的每个矩阵到底在管什么2.1 目标函数与广义特征值问题SDA的完整目标函数一般写成argmax_w (w^T S_b w) / (w^T (S_t β X^T L X) w)其中分母里的三个部件各管一摊S_b类间散度。衡量各类中心相对全局中心的离散程度保证投影后不同类别尽量分开。这个和LDA的S_b完全一样。S_t总散度。衡量所有样本包括无标签相对全局均值的离散程度。用S_t而不是S_w的原因在于S_w只能由有标签样本计算而S_t可以纳入无标签样本对方差的贡献本身就带了一点半监督味道。X^T L X流形正则项。X是n×d的样本矩阵L是n×n的拉普拉斯矩阵。X^T L X是d×d的矩阵它刻画的是样本点在原始特征空间中的局部几何结构在投影方向上的“变形代价”。分母越大说明这个投影方向越容易破坏原有的近邻结构所以优化过程会同时压制这种方向。β控制“保持流形结构”和“最大化类间分离”之间的权衡。求解这个目标函数等价于求解广义特征值问题S_b w λ (S_t β X^T L X) w取前r个最大特征值对应的特征向量组成投影矩阵。这里有个老规矩要说S_b的秩最多是类别数减一c-1所以有效判别方向的个数也就只有c-1个。你要是降维目标超过c-1后面的方向基本都是数值噪声没有判别意义。2.2 近邻图与拉普拉斯矩阵的构建细节流形正则项的核心是拉普拉斯矩阵L它由近邻图衍生出来。构建过程分三步第一步把所有样本有标签无标签当作图的节点计算两两之间的距离。欧氏距离是最常用的度量但如果特征量纲差别大先做标准化否则距离会被量纲大的特征主导。第二步连边。每个样本和它的k个最近邻居连一条边边权重有两种常见做法0-1权重相连就是1不相连就是0。简单但损失了远近信息。热核权重w_ij exp(-||x_i - x_j||² / σ²)。距离越近权重越大更精细。我在实践中更推荐热核权重σ取每个样本到其k近邻的平均距离这样不同密度区域能自适应不会出现一片区域权重全部趋近于0。0-1权重省事但投影质量差一些特别是样本密度不均匀时。第三步由权重矩阵W计算拉普拉斯矩阵。常见的有三种组合拉普拉斯L D - WD是对角阵D_ii Σ_j W_ij规范化拉普拉斯L_norm I - D^{-1/2} W D^{-1/2}随机游走拉普拉斯L_rw I - D^{-1} W。SDA论文里用的是第一种组合拉普拉斯。我自己对比过规范化拉普拉斯在聚类不均衡时稍稳一点但差别不大。先按标准版本用就好。2.3 SDA、LDA、PCA三者之间的关系理解SDA最好的方式是把它放到和LDA、PCA的对比里看PCA只看总方差不管类别投影方向是方差最大的方向。它完全无监督但结果和分类目标经常不一致。LDA看类间/类内散度比方向判别性强但只用有标签样本标签少就翻车。SDA站在LDA这边同时用无标签样本补充结构信息。本质上它就是在LDA的广义特征值问题里给分母加了一个非负定矩阵βX^T L X。如果β0SDA退化成用S_t做分母的LDA变体。如果忽略S_b只保留正则项SDA就接近拉普拉斯特征映射。所以SDA更像一个“可插拔”框架想要多少判别性、多少流形保持靠β旋钮控制。3. MATLAB实现从零手写SDA核心代码3.1 算法主框架与输入输出设计写SDA的MATLAB实现我习惯把流程拆成四块散度矩阵计算、近邻图构建、拉普拉斯矩阵组装、广义特征值求解。函数签名如下function [W_proj, eigvals] sda(X, labels, k, beta, r) % SDA 半监督判别分析 % 输入: % X : n x d 数据矩阵每行一个样本 % labels : n x 1 标签向量约定0表示无标签样本正整数为类别号 % k : 近邻个数 % beta : 流形正则化系数 % r : 目标降维维度 % 输出: % W_proj : d x r 投影矩阵列向量为判别方向 % eigvals: r x 1 对应的特征值可观察各方向重要性这里有几个设计决策要解释一下。标签用0表示无标签是因为MATLAB的逻辑索引和unique处理正整数类别最方便而且sda函数内部用find(labels0)就能获得有标签样本下标简洁不绕弯。特征矩阵X按惯例是n行d列别弄反了否则后面矩阵维度全是乱的。3.2 近邻图构建与权重计算的实现近邻图构建是整个SDA里最费时也最容易出错的环节。最常见的问题有两个一是样本量大时pdist2生成完整的n×n距离矩阵内存直接爆炸二是热核带宽不会自适应。我的做法是先算每个样本的局部平均距离作为σ²再按局部σ²计算热核权重避免用一个全局固定带宽导致稀疏区域权重全为零% 欧氏距离矩阵n较大时建议用分块计算 D2 pdist2(X, X, squaredeuclidean); n size(X, 1); W zeros(n, n); for i 1:n [~, idx] sort(D2(i, :), ascend); nbs idx(2:k1); % 去掉自身 sigma2 mean(D2(i, nbs)) eps; % 局部自适应带宽 for j 1:k W(i, nbs(j)) exp(-D2(i, nbs(j)) / sigma2); end end W (W W) / 2; % 对称化保证拉普拉斯矩阵的对称性这段代码有几个细节值得展开。sort排序拿最近邻k1是因为第一个必然是它自己所以从第2个开始取。热核距离用平方欧氏距离和公式里的一致不需要再开方。最后对称化很重要因为理论上近邻关系是近似的i是j的近邻不代表j是i的近邻不处理会得到非对称的W后续拉普拉斯矩阵的特征分解可能出复数。3.3 目标矩阵组装与广义特征值求解散度矩阵和正则矩阵的组装代码逻辑清晰但容易在维度上出错。完整主函数如下function [W_proj, eigvals] sda(X, labels, k, beta, r) [n, d] size(X); % 数据中心化 Xc X - mean(X, 1); % ---------- 1. 总散度矩阵 S_t ---------- St Xc * Xc; % d x d % ---------- 2. 类间散度矩阵 S_b ---------- labeledIdx find(labels 0); knownLabels labels(labeledIdx); classes unique(knownLabels); c numel(classes); Sb zeros(d, d); for i 1:c idx_i labeledIdx(knownLabels classes(i)); n_i numel(idx_i); m_i mean(Xc(idx_i, :), 1); % 1 x d Sb Sb n_i * (m_i * m_i); % d x d end % ---------- 3. 近邻图与拉普拉斯矩阵 ---------- D2 pdist2(X, X, squaredeuclidean); Wmat zeros(n, n); for i 1:n [~, idx] sort(D2(i, :), ascend); nbs idx(2:k1); sigma2 mean(D2(i, nbs)) eps; for j 1:k Wmat(i, nbs(j)) exp(-D2(i, nbs(j)) / sigma2); end end Wmat (Wmat Wmat) / 2; Dmat diag(sum(Wmat, 2)); L Dmat - Wmat; % 流形正则项 R X * L * X R Xc * (L * Xc); % 先算L*Xc省一次大矩阵乘法 R (R R) / 2; % 数值对称化 % ---------- 4. 目标矩阵与广义特征值求解 ---------- M St beta * R; M M 1e-8 * eye(d); % 数值稳定防止奇异 % 广义特征值分解取前r个最大特征值对应向量 [V, D] eig(Sb, M); [eigvals, ord] sort(real(diag(D)), descend); V real(V(:, ord)); W_proj V(:, 1:r); eigvals eigvals(1:r); end有几个地方我想重点说一下。R Xc * (L * Xc)这种写法比(Xc * L) * Xc更好因为L是n×n矩阵很大后者先生成d×n的中间矩阵内存更大。在样本数远大于特征数时先算L*Xc是n×d再左乘Xc变成d×d整个过程内存占用小很多。M M 1e-8 * eye(d)这行代码是必须的。eig(A, B)要求B可逆而S_t加上正则项理论上正定实际数值上可能接近奇异尤其特征数d很大而样本量n较小时。加一个小对角扰动保平安量级别太大1e-6到1e-8足够。取前r个最大特征值对应的特征向量前一定要sort。MATLAB的eig(A, B)不会自动按特征值大小排序不排序直接取前r列会取到一堆没用的方向。3.4 完整调用示例与结果解读写一个可以直接跑的demo用内置的鸢尾花数据只给每类两个标签剩下的全当无标签样本load fisheriris X meas; % 150x4三种花 labels zeros(150, 1); % 每类取2个有标签样本 labels(1:2) 1; labels(51:52) 2; labels(101:102) 3; % 标准化距离度量更合理 X zscore(X); % 训练SDA降到2维 k 5; beta 1; r 2; [W_proj, eigvals] sda(X, labels, k, beta, r); % 投影 Z X * W_proj; % 可视化 figure; gscatter(Z(:,1), Z(:,2), labels); title(SDA投影结果);跑完你会发现即使每类只有两个标签投影后的三个类别依然能肉眼分开。而同样条件下LDA的投影往往会把其中两类挤在一起。这就是SDA的价值。特征值eigvals可以拿来当“方向重要性”的参考。比如第一个特征值远大于第二个说明主要可分方向只有一个数值上特征值还能反映类间分离和流形保持的比值关系做调参分析时会用到。4. 实验对比SDA在合成数据和真实数据上的表现4.1 合成实验三簇数据加少量标签为了直观验证SDA的行为我先生成三簇高斯数据每簇300个样本三维特征簇中心分别在(0,0,0)、(4,0,0)、(0,4,0)附近。每类只给2个标签其余全部按0处理rng(42); X [randn(300,3); randn(300,3)repmat([4,0,0],300,1); randn(300,3)repmat([0,4,0],300,1)]; labels zeros(900,1); labels([1 2]) 1; % 第一类标签 labels([301 302]) 2; % 第二类标签 labels([601 602]) 3; % 第三类标签分别用PCA、LDA、SDA降维到2维然后看投影散点图。PCA完全无视类别三簇在某个视角下可能叠在一起LDA因为只用6个标签样本估计类均值偏差大投影方向经常偏向某一个错误的旋转角SDA因为把900个样本的几何结构都用了投影方向明显更接近数据的自然分离方向。这种合成实验跑起来很快是检验SDA代码实现是否正确的好方法。如果SDA投影结果还不如LDA大概率是近邻图或者拉普拉斯矩阵构建出了问题。4.2 真实数据实验标签比例与分类精度更接近实际场景的实验是固定一个数据集逐步增加每类标签数量观察分类精度变化。我用手写数字0/1/2三类的子集做过一组实验像素数据先降采样到8×8也就是64维再用PCA预处理到30维这一步是为了消除原始像素的强相关性。分类器用最简单的1-NN在投影空间里评估。在“每类只有1~5个标签”的区间里SDA对LDA的优势非常明显。下面是这组实验里我得到的一组典型结果具体数值会随随机种子波动但趋势稳定每类标签数PCALDASDA (k5, beta1)134.833.245.6236.140.763.9537.561.874.21038.272.576.42039.080.381.15039.682.782.4这个表透露了几个关键信息。标签极少时LDA甚至不如PCA因为它用极少的标签样本估计出来的方向噪声很大还不如无监督的PCA稳。SDA在标签少时优势最大每类5个标签时比LDA高12个百分点左右。随着标签数量增加LDA慢慢追上来最终两者趋同。这说明SDA的本质就是在标签不足时用无标签数据“补位”不会给充分标注场景带来额外提升这点认知很重要别指望SDA是万能药。4.3 标签比例变化时SDA的优势区间在哪从上面的趋势可以总结出一个经验判断当每类有标签样本数少于20个时SDA通常值得一试当标签比例超过50%时SDA与LDA的差距基本消失而SDA的构图和求特征分解开销反而成了负担。还有一个容易忽略的现象无标签样本的“质量”也会影响SDA。如果无标签样本和真实数据分布差异很大比如混入了大量噪声样本或异常点流形正则项反而会把投影方向带偏。这种时候减小β或者增加k值能部分抵消噪声点的影响。所以在实际任务里给SDA之前先洗一遍数据过滤掉明显的异常样本比纠结参数管用得多。5. 参数调优与踩坑实录5.1 k和热核带宽怎么选k是近邻个数经验范围在5~15之间。k太小近邻图会碎成多个不连通子图拉普拉斯矩阵的谱结构不稳定k太大不同类别的样本被连在一起流形结构被“抹平”正则项失去意义。我一般先用k5跑一遍再试7、10观察投影结果稳定性。没有硬性最优和样本量、类别重叠度相关。热核带宽我强烈建议用每个样本局部自适应而不是全局固定σ。全局σ在小方差区域会让权重全趋近于1在稀疏区域又全趋近于0近邻结构被淹没。局部自适应虽然计算量大一点但鲁棒很多。5.2 beta正则化参数的经验规则β控制流形正则项的强度是SDA里最敏感的参数。直觉上理解β很小SDA退化成LDA标签不足时效果差β很大投影方向被几何保持主导判别信息被稀释类间可能分不开β适中两者平衡效果最好。寻找β的经验做法是logspace网格搜索然后配合交叉验证。比如beta_list logspace(-1, 3, 20);从0.1到1000每档跑一遍在验证集上看分类精度。实际项目中我观察到最优β常落在1~10这个区间但这个规律不代表所有数据都适用还得自己搜。还有一点如果特征维度很高而样本量相对小β稍微调大能压制过拟合这点和岭回归的直觉一致。5.3 高维小样本场景的数值问题高维数据比如基因表达、图像像素直接展开跑SDA最常遇到两个问题。第一个是内存爆炸。pdist2(X, X)会产生n×n的矩阵n5000就接近200MBn20000直接爆掉。解决办法是用knnsearch需要Statistics and Machine Learning Toolbox只找近邻不用算全距离矩阵或者分块计算距离只存储每行最小的k1个值。第二个是M矩阵接近奇异。特征维度d接近甚至超过样本数n时S_t是奇异的加1e-8的扰动可能不够。我的习惯是先对X做PCA降到min(n*d, 100)维再跑SDA。这不是什么花活而是一个标准预处理能把数值问题压下去而且不会损失太多判别信息。5.4 常见问题速查表现象可能原因解决办法eig报错“Matrix must be positive definite”M矩阵奇异增大对角扰动到1e-6或先PCA降维投影结果全是NaNM矩阵严重病态检查是否特征里有无穷值先标准化SDA效果不如LDAβ太小或k太大增大β搜网格减小k到5左右特征值出现复数矩阵不对称或有数值误差对称化所有中间矩阵取real部分高维数据直接报内存错误pdist2矩阵太大改用knnsearch或分块计算距离降维超过c-1后判别效果差有效判别方向只有c-1个r不要超过类别数减一6. 从SDA还能往哪走SDA这套思路本身是个开放的框架理解之后可以往多个方向扩展。核化版本用核函数把数据隐式映射到高维空间然后在核空间里做同样的散度比优化适合线性不可分数据。流形正则项也可以换比如换成局部线性嵌入的权重或者用更复杂的图构造方式。再进一步可以把SDA的目标函数丢进深度网络里当正则项让网络在少量标注下依然学到判别性表示。不过这些扩展都建立在同一个核心思想上无标签数据不是废物它的几何结构本身就携带大量信息。做实验的时候建议先吃透线性版本的SDA把近邻图、拉普拉斯矩阵、广义特征值求解这些零件都跑熟再去玩进阶花活。我在实际使用中的体会有几点。SDA不是用来替代LDA的而是当标签不够时用来“救场”的它最舒服的工作区间是每类标签只有十几个甚至几个的时候标签充足了反而没必要用。调试SDA时最先检查近邻图和拉普拉斯矩阵这两个环节出问题比调β更隐蔽也更致命。最后一个小技巧画投影散点图时把无标签样本单独用一种颜色画出来观察它们是否被投影到正确的类簇附近这是判断正则项是否生效最快的方法。