做多指标定量测定的人,十有八九都会被同一个问题卡住:指标测了一堆,数据表格填得满满当当,但领导或者审稿人一句“所以它到底属于哪个等级”,就让你不知道怎么回。项目名称里这串组合——PCA、OPLS-DA、FA、熵权TOPSIS——不是炫技,恰恰是我踩了不少坑之后沉淀下来的一套完整解决思路。这篇文章把整个流程掰开揉碎,从原理到MATLAB实现,从参数选择到避坑经验,一次性讲清楚,目标是让你拿到自己的多指标数据后,能照着搭出一条从“原始测定值”到“等级预测结论”的可靠链路。
做这事的场景很典型:食品掺伪鉴别、中药材道地性划分、环境水体质量分级、土壤重金属污染评价,甚至工业产品的质量分等。你手里通常有一批样本,每个样本测了若干个指标(比如化学成分含量、理化性质、传感器响应值),样本本身又分几个已知等级(优级、良级、一般/合格、不合格),新来一个未知样本,要预测它属于哪一级。PCA和FA负责把高维指标压缩成看得见的结构,OPLS-DA负责判别分类,熵权TOPSIS负责给出一个客观的综合评分和排序——五者各管一段,组合起来才是一个完整闭环。
1. 先把思路捋清楚:为什么要“五合一”
1.1 单模型各有短板,组合才有意义
很多人上来就只跑一个PCA,出一张二维得分图,然后指着一个聚类说“这大概是一类”。这在样本量小、类别少的时候勉强能看,但问题有两个:第一,PCA是无监督方法,它只抓最大方差方向,不关心你的分组标签,导致有时候最能区分组别的信息反而被压缩到后面的主成分里;第二,PCA给不出一个“新样本归谁”的量化判据。判断标准靠肉眼,这在发表论文或者实际质检时站不住脚。
OPLS-DA解决了“分类判据”的问题,但它本质上是判别分析,输出的是样本归属类别和预测概率,不是一个精细的等级排序。FA能从可观测指标里提炼潜在因子,方便解释“哪些指标是一伙的”,但同样不直接回答问题。熵权TOPSIS是综合评价里的经典组合,能把多维指标合成一个得分并排序,但它是纯数据驱动的静态评价,没用到已知类别的先验信息。
所以我的核心思路是:PCA先看全局结构,FA做潜在维度解释,OPLS-DA做有监督的定性判别,熵权TOPSIS做无监督的综合定量排序,最后用已知等级样本的TOPSIS得分分布作为切分依据,实现对新样本的等级预测。这四条腿走路,比任何一个单模型都稳。
1.2 整体技术路线:每一步对应一个明确产出
完整流程我用文字描述,不画图,你按顺序在MATLAB里执行就行:
- 原始多维测定矩阵X(样本×指标),先做数据清洗和标准化,产出干净的建模数据。
- 跑PCA,拿到特征值、方差贡献率、主成分得分和载荷,用碎石图和二维/三维得分图观察自然聚类。
- 跑FA(因子分析),得到因子载荷矩阵、旋转后的潜在因子和因子得分,解释指标间的潜在关联。
- 已知等级的训练集上跑OPLS-DA,得到区分样本类别的判别模型、VIP值、混淆矩阵,确认可判性。
- 全部样本用熵权法计算每个指标的客观权重,再跑TOPSIS,得到每个样本的相对贴近度Ci,按已知等级统计Ci区间,建立等级判别阈值。
- 新样本走同一套标准化与打分流程,先看OPLS-DA分到哪一类,再看Ci落在哪个阈值区间,两者互相印证,输出最终等级。
这套流程的妙处在于:它把“分类”和“排序”结合了。分类告诉你“变化趋势属于哪一群”,排序告诉你“在这个群里综合水平是优还是良”。
1.3 为什么用MATLAB而不是Python
不是Python不能做,而是MATLAB在这类多变量分析上有三个天然优势。第一,pca、factoran、plsregress这些函数属于官方工具箱,接口稳定、文档完善,出了bug好排查。第二,交互式环境对“跑一步看一步”的工作流非常友好,参数调整可以实时看到结果反馈。第三,矩阵化编程思维跟多元统计的数学表达高度一致,写代码时不容易出现转换错误。你在网上搜到的“matlab pca主成分分析”“matlab topsis综合评价法”大多是官方函数包装好的现成实现,这也是我推荐它的直接原因。
2. 数据准备与预处理:这一步决定后面所有结果
2.1 数据应该长什么样
我用一个虚构但非常接近实际任务的例子来说明。假设要判定一批食品样本的等级,每个样本测了7个指标:总黄酮含量(mg/g)、多糖含量(%)、总酚含量(mg/g)、灰分(%)、水分(%)、粗纤维(%)、DPPH自由基清除率(%)。共30个训练样本,三个等级:优级(10个)、良级(10个)、一般(10个),另外有5个未知样本要预测。
在MATLAB里,数据组织成两个矩阵即可:
% Data_X: 30x7 训练样本测定值 % Data_Y: 30x1 等级标签,1=优,2=良,3=一般 % New_X: 5x7 未知样本测定值 % var_names: 1x7 cell,比如 {'总黄酮','多糖','总酚','灰分','水分','粗纤维','DPPH'}这一条值得多说两句:指标的顺序、单位、量纲差异极大,有的含量是mg/g,有的是%,有的是百分率。如果直接扔进模型,量级大的指标会在距离计算里“称王称霸”,量级小的指标直接失去话语权。所以预处理不是可选项,是必选项。
2.2 标准化:用zscore还是极差归一化
这一步要有讲究。PCA、FA、OPLS-DA这类基于方差协方差结构的模型,我用的是zscore标准化(均值为0、方差为1),因为协方差矩阵在这里就是相关系数矩阵,保证每个指标在计算主成分时地位平等。
[X_norm, mu, sigma] = zscore(Data_X);TOPSIS那一环节就要换思路了。熵权法中熵的计算用的是概率p_ij,而概率要求非负,zscore之后的负值会出问题。所以到TOPSIS时,我会对正面指标(比如黄酮含量,越高越好)和负面指标(比如水分,有时越低越好)分别做极差归一化:
% 正面指标:指标值越高越好 x_norm = (x - min(x)) / (max(x) - min(x)); % 负面指标:指标值越低越好 x_norm = (max(x) - x) / (max(x) - min(x));我建议在项目刚开始时就把两套标准化都写好,PCA/FA/OPLS-DA用zscore版,TOPSIS用极差版。不要偷懒只做一套然后到处调用,后面会哭。
2.3 缺失值与异常值:先用箱形图过一遍
网上热词里有“根据箱形图检测异常值的matlab程序”,这在多指标测定场景里非常实用。测定过程难免有设备抖动、违样本污染、记录笔误,一个离谱的离群点能拽偏整个PCA的方向。我的习惯是预处理阶段先画箱形图:
figure; boxplot(X_norm, 'Labels', var_names); ylabel('标准化值'); grid on;看到箱外的点先不急着删,停一下想清楚:这个样本本身是不是有问题的实验样品?如果是正常样品但指标极端,那是真实差异,可能恰恰是分类的关键样本,删了反而坏事。只对明确属于“前处理失误”“仪器漂移”“记录错误”的点采用中位数填补或剔除。我的原则:异常值是敌人,极端样本是线索,不要混为一谈。
3. PCA和OPLS-DA:先看能不能分开,再问怎么分
3.1 PCA原理与MATLAB实现:为什么用协方差矩阵
PCA的基本逻辑是:原始p个指标之间通常强相关——比如总酚含量和DPPH清除率往往正相关,水分和灰分往往负相关——那么用p个坐标轴描述样本就产生了大量冗余。PCA的工作是把这些相关指标重新组合成互不相关的主成分,每个主成分是原始指标的线性组合,第一步找方差最大的方向,第二步在正交方向上找剩余方差最大的方向,依此类推。
这里经常有人问:为什么PCA要算协方差矩阵的特征值分解?协方差矩阵对角线是各指标方差,非对角线是两两协方差,它完整刻画了变量间的线性关系的“量的结构”。PCA本质上是在做协方差矩阵的谱分解,特征值就是各主成分解释的方差大小,特征向量就是新坐标轴的方向。一旦特征值小了,对应的特征向量方向上的信息就接近噪声。所以“用协方差矩阵特征值分解”不是拍脑袋,而是把“找最大方差方向”的优化问题转化成标准的线性代数问题。
MATLAB里不需要手写特征分解,一个函数就够了:
[coeff, score, latent, tsquared, explained] = pca(X_norm);coeff是载荷矩阵,7×7,每一列是一个主成分的载荷向量。score是主成分得分矩阵,30×7,每一行是样本在新坐标系里的坐标。latent是特征值,对应各主成分方差。explained是各主成分方差贡献率百分比。
跑完之后立刻看结果:
figure; subplot(1,2,1); plot(explained, '-o', 'LineWidth', 1.5); xlabel('主成分序号'); ylabel('方差贡献率 (%)'); title('碎石图'); subplot(1,2,2); gscatter(score(:,1), score(:,2), Data_Y, 'rgb', 'o^*'); xlabel('PC1'); ylabel('PC2'); title('PCA得分图'); grid on;这一步的核心价值不在分类,而是让你快速判断:如果前两个主成分的得分图已经能看出三团样本的大致分离,说明指标设计是有效的;如果完全糊成一团,后面硬跑OPLS-DA大概率还是分类一般,这时候回去查指标选得对不对,比调模型参数有用得多。
3.2 主成分个数:看累计贡献率,但不要强行85%
教科书说累计方差贡献率达到85%就够,但实际经验是:测定指标之间相关性越强,前两三个PC占的贡献率越高;指标独立性越强,需要的PC越多。我一般把主成分个数定为“累计贡献率刚超过80%”和“特征值大于1”两者的交集附近,同时尊重业务解释的容易程度。PC3、PC4载荷矩阵里如果有明确指向某类化学成分的载荷,即使贡献率只有6%,也保留。
有一个我踩过的坑:一开始做PCA只保留前两个主成分,画了张2D图,死活看不出分类趋势。后来把PC3也画进去才发现三组样本在PC3方向上分得清清楚楚。PCA前两个方向的方差最大,但最大方差方向不一定是分类方向,这在无监督降维里很常见。所以至少要看三维散点图,再用颜色标记类别。
3.3 OPLS-DA:把“能分开”变成“能被判别”
PCA告诉我样本有聚类趋势,但项目要求的是“等级预测”,我需要一个有监督模型直接输出“新样本属于哪一类”。OPLS-DA是偏最小二乘判别分析(PLS-DA)的升级版。它的核心思想是:把X矩阵里的变异分成两部分,一部分是与类别标签Y相关的预测信息,另一部分是与Y无关的正交变异(比如批次效应、仪器漂移),然后把正交部分从X里扣除,再用剩下的预测成分做判别。
说人话就是:PLS-DA的分类效果容易被与分类无关的巨大变异干扰,比如不同月份测定的系统性漂移可能比真实类别差异还大;OPLS-DA专门把这些干扰“扣”出来,让分类决策只依赖与标签有关的信息。这就是它名字里“O(Orthogonal,正交)”的意义。
MATLAB官方没有直接叫oplsda的函数,这反而是很多人卡住的地方。我实际用的是两种方案,任选其一:
方案A:安装第三方工具箱libPLS或者Eigenvector的PLS_Toolbox,有专门函数。
方案B:自己写一个简化的OSC-PLS流程,先用正交信号校正剥离与Y正交的X变异,再用plsregress跑PLS-DA。
我给出方案B的简洁实现框架,核心是OSC部分:
function X_osc = osc_filt(X, Y, n_components) % X: 样本×指标矩阵(已zscore) % Y: 类别标签,需要转为哑变量矩阵 % n_components: 需要剥离的正交成分数 X_osc = X; for k = 1:n_components % 1. 计算X与Y之间的权重 w = X'*Y / norm(X'*Y) w = X_osc' * Y; w = w / norm(w); % 2. 得分 t = X*w t = X_osc * w; % 3. 正交权重 w_orth = X'*t - w*(t'*t)/(w'*w)?? 这里做修正 w_orth = X_osc' * t - w * (t'*t) / (w'*w); w_orth = w_orth / norm(w_orth); % 4. 正交得分 t_orth = X*w_orth t_orth = X_osc * w_orth; % 5. 从X中减掉正交部分 p_orth = X_osc' * t_orth / (t_orth' * t_orth); X_osc = X_osc - t_orth * p_orth'; end end然后再用fitcdiscr或逻辑回归对振荡过滤后的X_osc做分类。这里要诚实提醒:自己写OSC容易在正交权重更新公式上出数值bug,初学者建议直接用libPLS。libPLS的MATLAB版本很轻量,解压后addpath(genpath('libPLS'))就能用,里面直接有plslda函数,交叉验证、VIP值都一起给了,省心太多。
OPLS-DA结果里,VIP分数(Variable Importance in Projection)是我必看的:VIP大于1的指标是对分类有显著贡献的核心指标,VIP在0.5到1之间的是边缘指标,小于0.5的大概率是凑数指标。这直接反哺实验设计:下次做多指标定量测定,可以砍掉一批VIP低的指标,省时省钱。
4. FA因子分析:找到藏在指标背后的“共同原因”
4.1 因子分析和PCA的区别,别搞混
PCA和FA在外观上很像,都是降维、都看载荷、都画得分图,但逻辑有本质区别。PCA做的是“方差分解”,它把总方差拆成互不相关的成分,不关心这些成分有没有业务含义。FA做的是“协方差结构分析”,它假设你测到的这些可观测指标背后有几个不可观测的潜在因子(比如“抗氧化活性”这个因子同时影响了总酚、黄酮、DPPH三个指标,“基础理化品质”这个因子同时影响水分、灰分、粗纤维),然后用“共性方差”解释指标之间的相关性。
所以FA的价值在解释,不在预测。它回答的是“为什么这些指标会同步变化”,而不是“样本怎么分类”。我在项目里用FA主要为了两件事:第一,验证多指标测定的指标体系结构是否合理,如果理论上一伙的指标(比如抗氧化相关)在旋转后确实载荷到同一个因子上,说明测定方案靠谱;第二,用因子载荷去辅助解读PCA/OPLS-DA的化学意义,写论文讨论部分时很有用。
4.2 factoran函数与方差最大旋转
MATLAB实现很直接:
% 提取3个潜在因子,用varimax旋转让载荷矩阵结构更清晰 [lambda, psi, ~, ~, stats] = factoran(X_norm, 3, 'rotate', 'varimax');lambda是载荷矩阵,7×3,每行是一个指标在3个因子上的载荷,载荷绝对值越接近1,说明这个指标和该因子关系越紧密。psi是特殊方差,是每个指标不能被公共因子解释的部分。stats里可以看p值,p值较大说明因子个数选择合理。
我一般会做一个可视化,把载荷矩阵画成条形图或热图:
figure; imagesc(abs(lambda)); colormap(parula); colorbar; xticks(1:3); yticks(1:7); xticklabels({'因子1','因子2','因子3'}); yticklabels(var_names); title('旋转后因子载荷绝对值热图');一个关键经验:因子个数一般设定在2~4个之间,靠特征值法和碎石图辅助判断。因子个数设多了,会出现指标被强行分裂到多个因子上,业务解释困难;设少了,共同因子解释不了足够多的方差,模型显得没什么用。另外KMO检验和Bartlett球形检验是跑FA前的基本体检:KMO大于0.6、球形检验p小于0.05,说明数据适合做因子分析。MATLAB里可以用corrcoef和cancorr的辅助函数实现,但即使不做这些检验,只要PCA得分图有结构,FA通常也不会差到哪去。
5. 熵权TOPSIS:把多维指标合成为一个等级分
5.1 熵权法计算权重:让数据自己说话
到这一环节,PCA、OPLS-DA已经把样本的结构、类别、判别规律摸清了,但“等级”本身需要量化。TOPSIS是最经典的多准则决策方法,核心思想不复杂:在一个多维空间里,理想样本是每个指标都取最优值的“正理想解”,最差样本是每个指标都取最劣值的“负理想解”,某个样本和正理想解越近、和负理想解越远,它的综合水平就越高。关键问题变成了每个指标的权重怎么定。
权重有两种来源:主观赋权(AHP、专家打分)和客观赋权(熵权法、CRITIC)。在多指标定量测定这类场景里,我强烈推荐熵权法,因为它完全不依赖人为主观判断,直接用数据的信息量来决定权重,审稿人和领导都不好质疑。原理是:某个指标在所有样本上的数值差异越大——分布越混乱,信息熵越大——它就越能区分样本水平,权重就该越高;反过来,如果某个指标所有样本都几乎一样,它对等级划分几乎没有贡献,权重就应趋于0。
计算过程分三步:
第一步,极差归一化得到R矩阵(样本×指标),这一步同时处理了正负指标方向,所有值在0到1之间。
function R = normalize_improve(X, impact) % impact: 1表示正面指标(越高越好),-1表示负面指标(越低越好) [n, p] = size(X); R = zeros(n, p); for j = 1:p if impact(j) == 1 R(:,j) = (X(:,j) - min(X(:,j))) / (max(X(:,j)) - min(X(:,j))); else R(:,j) = (max(X(:,j)) - X(:,j)) / (max(X(:,j)) - min(X(:,j))); end end R(R == 0) = 1e-10; % 防止log(0) end把零值替换成极小的正数这一步,看似不起眼,实际上是我吃过亏的地方。原始数据归一化后很容易出现0,直接代入熵公式会得到0 * log(0),NaN瞬间传染整个矩阵,后面的TOPSIS全废。这个问题必须提前处理。
第二步,计算每个指标的信息熵:
function e = calc_entropy(R) [n, p] = size(R); P = R ./ sum(R, 1); e = -1 / log(n) * sum(P .* log(P), 1); end第三步,由熵得到权重:
function w = calc_weight(e) d = 1 - e; % 信息效用值 w = d / sum(d); end这个权重我要多说一句:熵权法给的是相对权重,只反映这批样本间的区分能力,不等于指标的科学重要性。比如总黄酮含量在“分级”意义上的区分能力低于水分,可能因为优级和良级样本的黄酮含量本来差异不大,但这不代表黄酮不重要。写报告时把它描述成“基于信息量的客观权重”,千万不要说“总黄酮对品质不重要”,否则会被领域专家怼。
5.2 TOPSIS计算与等级梯度划分
权重算完,接下来是TOPSIS主流程:
function Ci = topsis_score(R, w) [n, p] = size(R); V = R .* repmat(w, n, 1); % 加权规范化矩阵 % 正理想解与负理想解 V_plus = max(V, [], 1); V_minus = min(V, [], 1); % 各样本到理想解的距离 D_plus = sqrt(sum((V - V_plus).^2, 2)); D_minus = sqrt(sum((V - V_minus).^2, 2)); % 相对贴近度 Ci = D_minus ./ (D_plus + D_minus); end得到全样本的Ci之后,用已知等级样本去建立等级阈值。做法很简单:把30个已知样本按等级分组,分别计算每组Ci的中位数和范围,然后观察分组是否紧凑。比如优级样本Ci在0.78~0.93,良级在0.56~0.75,一般在0.31~0.53。那么对新样本,先看OPLS-DA判它属于哪一类,再看它的Ci落在那一段,两者一致时直接下结论;不一致时以OPLS-DA的类别为准,但标注“综合得分偏低/偏高,建议复查测定数据”。
这一步往往能产生一个特别直观的三角图:横轴是OPLS-DA的判别得分或预测类别,纵轴是TOPSIS的Ci,不同等级样本自然分成三个横条,新样本落的位置一目了然。
6. 完整MATLAB实现:从数据到等级预测的一次跑通
6.1 主函数框架:我习惯的工程化写法
项目做得多了之后,我不喜欢把代码堆在一个脚本里,而是拆成两个层次:底层是各算法的函数文件(上面那些normalize_improve、calc_entropy、calc_weight、topsis_score其实都是独立函数),上层是一个主脚本控制流程。主脚本长这样:
%% 读取数据 Data_X = readmatrix('train_data.xlsx', 'Range', 'B2:H31'); Data_Y = readmatrix('train_data.xlsx', 'Range', 'I2:I31'); New_X = readmatrix('new_samples.xlsx', 'Range', 'B2:F6'); var_names = {'总黄酮','多糖','总酚','灰分','水分','粗纤维','DPPH'}; %% 预处理 [X_norm, mu, sigma] = zscore(Data_X); [New_X_norm] = (New_X - mu) ./ sigma; %% 1. PCA [coeff, score, latent, ~, explained] = pca(X_norm); %% 2. FA [lambda, psi] = factoran(X_norm, 3, 'rotate', 'varimax'); %% 3. OPLS-DA(以libPLS为例) Y_dummy = dummyvar(Data_Y); % 类别转哑变量 % 这里假设libPLS已加入路径 model = plslda(X_norm, Y_dummy, 3, 'scale', false); pred = plslda(model, New_X_norm); % 得到新样本的预测分类 new_class_pred = pred.y; %% 4. 熵权TOPSIS impact = [1 1 1 -1 1 -1 1]; % 根据业务设定正负指标方向 R = normalize_improve(Data_X, impact); e = calc_entropy(R); w = calc_weight(e); Ci_train = topsis_score(R, w); % 新样本归一化时要用与训练集相同的极值 R_new = normalize_improve_new(New_X, Data_X, impact); % 需要额外写一个函数 Ci_new = topsis_score(R_new, w); %% 5. 等级阈值与预测 % 用已知样本Ci和Data_Y计算每组区间 grade_table = table(); for g = 1:3 idx = (Data_Y == g); grade_table.range(g, :) = [min(Ci_train(idx)) max(Ci_train(idx))]; grade_table.median(g) = median(Ci_train(idx)); end disp(grade_table);这里有个容易踩的细节:新样本做TOPSIS时,R_new的极差归一化必须用训练集的min和max,不能用新样本自己的min和max。否则新样本的取值范围变了,权重和理想解的概念就全乱了。我专门写了normalize_improve_new函数,内部传入训练集作为参照范围。
6.2 训练集交叉验证:别让模型看起来比实际准
无论OPLS-DA还是等级阈值,都要做交叉验证。我通常的做法是留一交叉验证(LOOCV),因为样本量不大(30个),LOOCV比K折更能充分利用每个样本。用混淆矩阵看判别准确率:
% libPLS中plslda自带交叉验证功能,输出混淆矩阵和准确率 cv = crossval(model, 'LOO', true); confusionchart(cv.y, Data_Y);如果准确率低于80%,先别急着调模型,回头看点:分类界限到底清不清楚?是不是某些样本本身的标签就是错的?如果第二步PCA得分图上两类有重叠,OPLS-DA准确率大概率也上不去,这时候不是模型的问题,而是指标体系的信息量不够——要么加指标,要么换更灵敏的测定方法。
对于TOPSIS的等级阈值,我也做类似验证:训练集30个样本里每次留1个,把它的Ci和阈值比较看是否判对。这个验证结果往往和OPLS-DA的准确率相互独立,构成了一个交叉印证的证据链。
6.3 可视化:一张图把结论说清楚
一个完整项目跑完,我至少会出四张图,按汇报顺序排序:
第一张:PCA得分图(前三主成分三维图),展示样本自然分布和类别着色。第二张:OPLS-DA得分图(预测成分t1 vs 正交成分t1),展示判别效果;第三张:因子载荷热图,展示指标体系的内在结构;第四张:TOPSIS贴近度分布图,横轴样本编号,纵轴Ci,用不同颜色标等级区域,新样本用黑点标出来。
第四张图我尤其推荐,它的信息密度能顶半页文字:既告诉别人你的等级划分边界在哪,又能直观地看到新样本落在什么位置,有没有擦边球。汇报时所有指标数据都可以砍,这张图必须放。
7. 常见问题与排查清单:我替你先趟过的坑
7.1 问题速查表
把这个表保存下来,遇到问题直接对号入座:
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| PCA得分图一团模糊,无聚类趋势 | 指标间本身区分能力弱 | 换指标组合;增加测定灵敏度 |
| PCA前两个PC贡献率极低(低于50%) | 原始指标独立性过强 | 尝试FA代替PCA做维度综合;PC数量多取几个 |
| OPLS-DA交叉验证准确率远高于预期 | 存在过拟合或数据泄露 | 检查预处理是否混入测试集信息;用LOOCV重跑 |
| FA载荷矩阵中出现负号难解释 | 指标方向未统一 | 对负向指标先取倒数或反向编码 |
| TOPSIS出现NaN | 归一化后0值直接取log | 把0替换为1e-10 |
| 新样本的Ci全部落在阈值边界上 | 阈值过窄或最近一批测定存在系统偏差 | 用ROC曲线重新定阈值;检查是否有批次效应 |
| 同一样本重复测定后Ci波动大 | 测定重复性差 | 增加平行测定次数取均值 |
7.2 OPLS-DA与TOPSIS结果矛盾时听谁的
这个问题我遇到不止三次。新样本的OPLS-DA判为“优级”,但TOPSIS贴近度只有0.45,落在“一般”区间。一开始我以为是BUG,后来想明白了:OPLS-DA用的是整体代谢轮廓或化学成分组合的相似性,它说“这个样本的谱学特征最接近优级训练样本”;TOPSIS用的是单指标加权后的综合评分,它说“按这批指标逐项加权,它的得分不算高”。两个模型评判维度不同,矛盾不奇怪。
我的处理逻辑:如果OPLS-DA的预测概率非常高(比如95%以上),且TOPSIS得分只是略低于阈值,我采信OPLS-DA,但会在报告里注明“综合评分位于等级边界附近,建议重复测定确认”;如果两个模型结论完全相反,坚决先查原始数据有没有异常,别急着下结论。这种矛盾本身也是多指标测定的价值所在——它提醒你单一维度评价可能失真。
7.3 样本量小的时候怎么稳住模型
多指标测定项目经常面临一个尴尬:指标10个,样本就20个。PCA还好,OPLS-DA很容易过拟合。我的经验是:
- 用交叉验证当作准确率的唯一依据,绝不用回代准确率(训练集本身的正确率没有说服力)。
- OPLS-DA的潜在变量数(latent variables)宁少勿多。用2~4个足够,变量数越多过拟合越严重。libPLS里可以用交叉验证选择最优潜在变量数量,别拍脑袋定。
- FA的因子个数同样宁少勿多,3个因子解释不了全部也没关系,只要业务可解释就行。
- 如果样本量不足30,熵权TOPSIS的权重会不稳定,这时候可以对比一两次重复抽样的权重,如果权重向量方向基本稳定,说明结论可信。
7.4 指标方向设定的业务逻辑
TOPSIS的impact向量必须由熟悉业务的人确认,这是整个模型里唯一“主观”的地方。以食品等级为例:总黄酮、多糖、总酚、DPPH清除率是越高越好,水分、灰分、粗纤维则要看具体产品品类,有的产品水分越低越好,有的产品对水分有特定标准,过高过低都不行。对于“适中最好”的指标,要用理想型归一化,达到区间中点时得分最高,偏离时递减。这个MATLAB实现起来不复杂,但需要你提前把每个指标的方向都敲定,否则算出来的权重和得分方向反了,结论直接翻车。
一些实际操作的体会
这套流程我前前后后跑过十几个项目,最大的感悟是:多指标定量测定本身不难,难的是别让数据白测。很多人花大把精力在测定上,结果到了数据分析阶段要么只会跑个PCA画个圈,要么只拿Excel算个加权平均分,白白浪费了数据里藏着的判别信息。PCA、OPLS-DA、FA、熵权TOPSIS这组组合,恰恰补上了从“测出数值”到“说出结论”之间的整条链路:无监督方法帮你发现结构,有监督方法帮你建立判别,综合评价方法帮你量化排序,三者互为印证,结论比任何单一方法都站得住。
代码务必按“函数文件+主脚本”的思路整理,保存好每次运行的参数和阈值,特别是训练集的均值和标准差、极差归一化的min和max,这些是复现结果的核心资产。下次新样本进来,十分钟就能完成一次完整的等级预测。
这个方法换一批指标和样本之后,完全可以迁移到你自己的项目里。只要把指标名、正负方向、等级标签替换掉,代码骨架直接复用。如果你在复现时卡在某个环节,或者遇到了上面表格里没列进来的怪问题,带着你的数据和报错信息来交流,我见过的坑大概率比你想的要多。