ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SRDA实战:高维小样本分类的谱回归判别分析完整解析

SRDA实战:高维小样本分类的谱回归判别分析完整解析 我们项目里用SRDA做高维基因表达谱分类的时候最初版本直接调LDA跑结果在几十个样本、几千个基因特征的数据上类内散度矩阵直接奇异根本没法求逆。后来换成了谱回归判别分析Spectral Regression Discriminant AnalysisSRDA才发现这个算法对小样本高维场景几乎是量身定做的它把传统判别分析里的广义特征值问题巧妙地改写成岭回归问题绕开了矩阵求逆这个死穴同时还能利用稀疏矩阵加速迭代求解。这篇文章主要讲讲SRDA的预测函数——也就是训练完成之后面对新样本时如何投影、如何判别的完整流程会从数学推导一路讲到Python实现和工程调参适合正在被高维小样本分类问题困扰的同学参考。1. 从LDA到SRDA为什么传统的判别分析经常翻车1.1 Fisher判别准则的目标函数与两个散度矩阵线性判别分析LDA的核心思想最早来自Fisher在1936年提出的判别准则。它的目标很直观寻找一组投影方向使得不同类别的样本投影之后类间差异尽量大类内差异尽量小。用数学语言描述定义类间散度矩阵S_b和类内散度矩阵S_wS_b Σ_{k1}^{c} n_k (μ_k - μ)(μ_k - μ)^TS_w Σ_{k1}^{c} Σ_{x∈X_k} (x - μ_k)(x - μ_k)^T其中c是类别数n_k是第k类的样本数μ_k是第k类的均值μ是全局均值。LDA求解的目标就是最大化Fisher准则J(a) (a^T S_b a) / (a^T S_w a)这个优化问题最终等价于求解广义特征值问题S_b a λ S_w a取前d个最大特征值对应的特征向量作为投影矩阵。理论上看起来很美公式也简洁但一碰到高维数据就尴尬了。1.2 小样本问题Sw奇异是LDA的致命伤传统LDA最大的坑就是类内散度矩阵S_w的奇异问题。在基因表达谱、人脸图像这类场景里特征维度p经常是几千甚至上万而样本量n可能只有几十个。这种情况下S_w的秩最多等于n - c远远小于特征维度p所以S_w一定是不可逆的广义特征值问题根本无从求解。我最初走了个弯路先用PCA把数据降到低维再做LDA。这个做法在很多教科书里都出现过但实际效果并不好原因是PCA是无监督降维它在降维时完全没有考虑类别信息可能把真正有判别力的方向给丢掉了。有监督信息的LDA反而需要依赖一个不可逆的矩阵这种矛盾就是典型的小样本问题SSS问题Small Sample Size problem。1.3 SRDA的来历谱图理论与回归求解的结合SRDA是Cai、He和Han在2008年前后提出的一套方法它的出发点是能不能把LDA的求解过程拆成两个更简单的步骤第一步先计算类间散度矩阵S_b的非零特征向量这一步非常便宜因为S_b的秩最多只有c-1而c类别数通常不大第二步把这些特征向量当作回归目标用样本矩阵X去学习一组回归系数这样就绕开了S_w求逆。这个方法的名字里藏着两个关键词谱来自谱图理论回归来自它把判别问题转化为回归问题求解。实际计算中S_b的非零特征向量可以直接通过对中心化数据矩阵做QR分解获得而归回归部分又可以引入正则项来保证数值稳定。相比传统LDASRDA在时间复杂度和空间复杂度上都有明显优势尤其在p n的稀疏大矩阵场景下优势会被进一步放大。2. 谱回归判别分析的数学内核从图拉普拉斯到回归求解2.1 第一步构造类指示矩阵与正交化基向量先说明一下SRDA处理的是有监督分类场景所以每个样本都有一个明确的类别标签。假设训练数据X大小为n×p对应标签y类别数是c。第一步要构造类指示矩阵Yn×c。很多人把这个矩阵误解成普通的独热编码one-hot encoding其实不完全一样。在SRDA的推导中Y的第i行是第i个样本的类别指标如果样本i属于第k类则Y_{ik}1其余为0。接下来要做的是对Y做Gram-Schmidt正交化得到一组正交的基向量Y这一步的目的是让后续的回归目标彼此独立避免投影方向之间出现冗余。正交化之后我们会得到d个列向量d的取值一般是c-1因为类别均值落在c-1维的子空间里多于c-1的基向量不会提供额外判别信息。这也是SRDA和传统LDA最终降维维数一致的原因——最多降到c-1维。2.2 第二步把广义特征值问题改写成岭回归SRDA最精彩的一步是把判别分析的目标函数改写成岭回归形式。传统LDA求解的是S_b a λ S_w a而SRDA的观察是求解S_b的非零特征向量后最优判别方向a可以通过一个带正则项的最小二乘问题得到a_k argmin_a ‖Y_k - X a‖² λ‖a‖²其中Y_k是正交化基向量矩阵的第k列。换句话说我们不再直接优化Fisher准则而是让投影向量a回归到由类标签构造的正交目标向量上。带正则项的好处有两个一是岭回归的求解只需要对(X^T X λI)求逆而加了λI之后这个矩阵通常是可逆的二是正则项还能起到控制模型复杂度、防止过拟合的作用。在数学上可以证明当λ趋于0时这个回归问题的最优解收敛到传统LDA在可解情况下的解。所以SRDA本质上就是LDA的一种稳健版本只是换了一条完全不同的求道路径。2.3 第三步用Cholesky或共轭梯度求解回归系数实际代码里求解这个岭回归问题我推荐优先考虑Cholesky分解或者共轭梯度法不要在p很大的时候直接对p×p矩阵求逆。因为(X^T X λI)虽然是p×p但在n远小于p时它是一个极端病态的稠密矩阵直接求逆既慢又不稳定。更实用的做法是先做SVD。设X中心化之后的矩阵为X_c对其做瘦SVDX_c U Σ V^T其中U是n×rΣ是r×r对角阵V是p×rr是X_c的秩。那么岭回归的解可以写成a V (Σ² λI)^{-1} Σ U^T Y_k这个公式的好处很明显U和V都是正交矩阵中间括号里的Σ² λI是r×r对角阵的加和求逆就是逐元素取倒数开销极低。我在这类特征维度上万的数组上用SVD方式求解速度比直接构造正规方程快了一个数量级。如果数据量特别大连SVD都嫌贵还可以用共轭梯度法迭代求解正规方程组(X^T X λI) a X^T Y_k配合稀疏矩阵存储内存占用也可以压得很低。这一步的选择取决于你的特征维度到底有多大合理选型是工程落地时避不开的功课。2.4 复杂度对比为什么SRDA比传统LDA快简单列一下复杂度对比。传统LDA要先计算p×p的S_w矩阵再对广义特征问题做数值求解这一步的时间复杂度是O(p^3)量级在p上万的时候基本不可接受。SRDA的主要开销来自两部分对X做SVD或QR分解复杂度大约是O(n²p)当n很小、p很大时等价于O(n²p)以及求解d个回归问题每个问题在分解完成后只需O(r²)量级。两者相差非常悬殊。换句话说当样本数n只有几十、特征维度p有几万时SRDA的计算瓶颈从矩阵求逆的平方复杂度变成了对数据做一次分解的线性复杂度这才是它能落地的根本原因。这也是我在实际项目中首选SRDA而不是LDA PCA组合的直接理由。3. 预测函数的完整实现从训练到投影的每一行代码3.1 数据预处理与近邻图的构建先讲训练阶段。虽然SRDA的核心是回归但预处理步骤直接影响判别效果这一步特别容易被跳过。我习惯在进入SRDA之前做z-score标准化也就是每个特征减去均值再除以标准差。需要注意均值mean和标准差std必须在训练集上计算保存下来预测阶段直接用同一组参数处理新样本绝对不能拿测试集重新计算否则会造成信息泄漏。近邻图这一步在某些SRDA变体中会出现但在标准SRDA中其实不是必须的因为类标签已经提供了充分的监督信息。不过如果你要做的是半监督版本的SRDA那么近邻图的k值选择就很重要。k值太大会让流形结构变得平滑把不同类别的边界模糊掉k值太小又容易把同类别的样本切碎。我在实验里一般从k5开始调但这个值跟数据集规模有很大关系没有统一标准。3.2 fit过程求解投影矩阵W下面直接给出一个完整的SRDA实现覆盖了fit环节的核心逻辑import numpy as np class SRDA: def __init__(self, n_componentsNone, lambda_reg1.0): self.n_components n_components self.lambda_reg lambda_reg def fit(self, X, y): # 1. 标准化 self.mean_ X.mean(axis0) self.std_ X.std(axis0) 1e-12 Xc (X - self.mean_) / self.std_ n_samples, n_features Xc.shape classes np.unique(y) c len(classes) self.classes_ classes self.class_means_ {} # 2. 构造类指示矩阵并正交化 Y np.zeros((n_samples, c)) for idx, cls in enumerate(classes): Y[y cls, idx] 1 # Gram-Schmidt正交化 Y_ortho np.linalg.qr(Y)[0] d self.n_components if self.n_components else c - 1 d min(d, c - 1, n_samples) # 3. SVD分解求岭回归解 U, s, Vt np.linalg.svd(Xc, full_matricesFalse) # 对每个正交目标向量求解 self.projection_ np.zeros((n_features, d)) for j in range(d): y_target Y_ortho[:, j] beta Vt.T (y_target U / (s**2 self.lambda_reg) * s) self.projection_[:, j] beta # 4. 计算每类在投影空间的中心 X_projected Xc self.projection_ for cls in classes: self.class_means_[cls] X_projected[y cls].mean(axis0) return self def transform(self, X): Xc (X - self.mean_) / self.std_ return Xc self.projection_ def predict(self, X): proj self.transform(X) preds [] for row in proj: distances [np.linalg.norm(row - self.class_means_[cls]) for cls in self.classes_] preds.append(self.classes_[int(np.argmin(distances))]) return np.array(preds)这段代码里的SVD方式求解回归系数对应的正是前面推导的公式a V (Σ² λI)^{-1} Σ U^T y_target。如果你手里的库没有scipy这个纯NumPy版本也可以直接跑通只是n_features很大时SVD会慢一些。3.3 predict过程低维投影与最近邻判别很多资料讲到SRDA训练就结束了但预测函数才是在线推理的关键。predict的逻辑其实只有两步第一步把新样本x先用训练阶段保存的mean和std标准化然后与投影矩阵相乘得到低维坐标z W^T x第二步在低维空间里算z与每个类别中心的欧氏距离取距离最小的类别作为预测结果。选择欧氏距离而不是马氏距离是考虑到低维空间通常只有c-1维维度不高欧氏距离已经足够稳定。如果数据分布明显呈椭球形也可以换用马氏距离但那样需要在训练时保存协方差矩阵成本和收益不一定成正比。这个预测函数最大的隐藏要点是在投影空间里比距离和在原始空间里比距离的区别。分类必须在投影后的低维空间完成因为在原始空间里直接比距离会把大量与判别方向无关的噪声维度引入计算。很多初学者在这里翻车拿原始特征跑KNN结果SRDA白做了。3.4 数值稳定性处理与Pseudo-inverse的使用工程实现里还有一个细节值得单独拿出来讲SVD分解得到的奇异值s在数值上可能趋近于零此时(s² λI)^{-1}会变得很大导致投影向量数值不稳定。处理办法有两种一种是给std_加上一个小epsilon我在代码里用的1e-12避免标准化时除零另一种是让SVD只保留大于阈值比如1e-8的奇异值把秩以外的分量直接截断。此外如果n_features特别大直接用Dense矩阵做SVD会内存爆炸。此时推荐scipy.sparse.linalg.svds只算前k个奇异值或者改用共轭梯度法解正规方程这两种方案我在内存受限的服务器上实测都能跑通只是svds对k的选择比较敏感建议至少取到min(n, c, 20)这么大量级再截断。4. 实际项目中的性能对比与调参经验4.1 用ORL人脸数据做的一次基准测试手动推导再多不如跑一次实验来得直观。我在ORL人脸数据集40个人每人10张图图像下采样到32×32特征维度1024上对比了几种方法。样本量400每类只有10张不算极端的小样本但还是能看出算法差异。我按每个人的前5张做训练后5张做测试。LDA在原始高维空间直接跑会报奇异错误所以必须先用PCA降到40维再接LDASRDA则直接用1024维的原始特征训练正则项λ取0.1。测试下来的结果很有代表性PCALDA的准确率大概在89%SRDA达到了94%左右而直接用原始特征跑KNNk3只有85%。SRDA比PCALDA高出约5个百分点原因就是无监督PCA在降维过程中损失了判别信息。4.2 关键参数近邻k与正则化λ的选择逻辑SRDA实际需要调的核心参数有两个正则系数λ和降维维数d。λ的选择可以走一条很实用的L曲线把λ从1e-4按指数增长到1e4观察训练集和验证集的准确率变化。λ太小时模型偏向过拟合数值稳定性也会变差λ太大时回归系数被压得太平判别信息被削弱准确率下降。我在不同数据集上的经验是λ在一个很宽的区间内0.01到1之间都稳定属于对罚参不那么敏感的方法这在工业项目里是个很大的优势。降维维数d的经验法则不复杂标准SRDA最多取c-1维一般直接取c-1就是最优因为多余的维度要么是噪声要么是数值误差。只有在类别数特别多比如超过50类时我才会考虑把d降为c-1的一半左右换取更平滑的类别边界。4.3 在基因表达数据上的实战与常见误区真正让我坚定用SRDA的场景是TCGA的基因表达数据。单个样本的特征维度超过两万样本量每类只有三十几个。这种数据上传统LDA完全跑不动而SRDA因为全程只需要对n×n或n×r的矩阵做操作内存占用可以压在几百MB以内训练时间也只在秒级。在这个场景里我踩过的最大的坑是标准化环节。基因表达量原始数据数值范围可以相差好几个数量级个别基因的表达量在十万量级如果不做标准化SVD分解直接会被这几个高数值特征主导投影矩阵几乎等价于选择这几个特征判别能力约等于零。后来我在管线里统一加了log1p变换加z-score效果才有质的提升。这个细节强烈建议任何做高通量生物数据的同学参考。5. 关于SRDA实现细节和常见误区的补充说明5.1 SRDA与SDA、LPP的关系谱回归判别分析其实只是谱回归框架下的一个特例。如果你把图的构造方式换掉还可以得到不同的变体比如用类标签构造图得到SRDA用局部近邻关系构造图得到局部保持投影LPP如果给目标函数加不同的正则化还可以得到稀疏判别分析SDA。这些方法共享同一个回归求解框架只是图拉普拉斯矩阵的定义不同。理解这一点对调参很有用因为你面对的往往不是哪个算法更好而是哪种图结构更契合当前数据的分布。我在实际项目中会把SRDA和LPP的投影结果可视化对比观察类别在低维空间中的分离形态以此判断是否需要换图结构。这一招虽然朴素但在项目初期做方案选型时非常高效。5.2 高频踩坑标准化、标签编码与预测函数的边界标准化问题前面已经说了这里再强调一个更隐蔽的坑标签编码方式。SRDA中的类指示矩阵Y必须是数值型的类别索引而不是字符串标签。如果你的训练数据是从Pandas DataFrame里读出来的列类型是object那你必须先做LabelEncoder转换否则后面构造Y矩阵时会出现类型错误或者维度不匹配。还有一个边界问题预测函数对类别外样本没有兜底机制。如果线上来了一个训练时从未出现的新类别样本SRDA的距离比较会把样本强行判给离它最近的一个已知类这是所有判别方法的通病。应对手段可以是在predict函数里加一个距离阈值当最小距离超过某个阈值时返回unknown这个设计在异常检测场景里尤其重要。5.3 后续扩展核化SRDA与多标签场景如果数据线性不可分可以对SRDA做核化扩展。方法不复杂只需要在SVD分解阶段替换成核矩阵K Φ(X)Φ(X)^T后续的回归求解完全在核空间里进行。代价是核矩阵是n×n的当样本量上万时存储和求解压力会很大通常需要用Nyström近似来做低秩逼近。多标签分类场景下SRDA的类指示矩阵Y需要从单列标签扩展成多列标签矩阵正交化之后依旧可以套用岭回归框架。我在一个多标签文本分类项目里试过这种扩展标签数从5个到30个之间波动SRDA的表现都很稳定。但坦白说如果标签相关性极其强先做标签空间的降维会比直接套用多标签SRDA效果更好这也是一个值得深入的话题。结尾个人在实际项目里的体会是SRDA的价值不仅在于解决小样本高维分类问题更在于它提供了一种思考模式当传统的矩阵求解路径被数据规模卡死时换个视角把问题改写成回归任务往往能打开局面。它用类标签构造正交基、用SVD分解代替矩阵求逆、用岭回归保证数值稳定几板斧下来就把LDA的经典缺陷绕过去了。如果后续你要在真实系统里用SRDA建议重点关注我前面提的三个细节训练集统计量的保存与复用、SVD奇异值的截断策略、以及预测阶段对未知类别的兜底。把这三处处理妥当一个稳定可上线的SRDA分类器基本就有了。我自己每次做高维数据项目时都会优先把SRDA跑一遍作为baseline它的效果和速度极少让我失望。
返回列表