CARS变量筛选算法:从近红外光谱到PLS建模的实用指南
简介:CARS(竞争性自适应重加权算法)可执行代码与文档整合包,面向化学计量学、光谱分析及机器学习研究者,解决高维数据下PLS回归的变量选择与过拟合问题。rar压缩包内共有37个文件,以32个m格式MATLAB源程序为主,另有2个mat实测数据集、1份PDF手册、1个doc说明和1张示意图,整体大小1.27MB。已有11425人学习下载。资料内含PDF操作手册、多种示例脚本与玉米光谱数据,覆盖CARS、SCARS、PLS、MCS等算法;代码含交叉验证、预处理、可视化等模块,便于理解自适应重加权迭代流程与RMSECV评估机制,可直接用于论文复现和实际光谱特征筛选。
1. 变量筛选为何要"竞争":CARS算法切入光谱建模的独特视角
近红外光谱数据动辄上千个波长点,变量比样本还要多,按常理信息越丰富模型越准,实测却往往是变量越多预测误差越大。因为波长点之间存在严重的共线性,冗余信息和不相关噪声会把偏最小二乘模型的回归系数拉扯变形,导致模型泛化能力急速下降。竞争性自适应重加权算法(CARS)要解决的正是这个矛盾:它不追求一次性找出"最优变量组合",而是通过模拟自然选择的迭代过程,让变量在竞争中逐轮淘汰,最终收敛到一个低维、低相关性的变量子集。理解CARS,就理解了变量选择里"随机但有方向"这一类方法背后的设计动机与底层机制。
2. CARS的骨架:蒙特卡洛采样与回归系数权重的底层逻辑
2.1 蒙特卡洛采样:为什么不看全量样本
CARS的第一步是"抽样"。每一轮迭代,算法会从校正集样本里随机抽取一定比例的样本(通常约80%),基于这些样本建立PLS模型。这里有个问题值得先想清楚:为什么不直接用全部样本建模?
我个人的理解是,变量选择的目标不只是找"哪些变量有用",还要找"哪些变量总是有用"——即鲁棒性。如果用全量样本只建一次模型,某个变量的回归系数可能是偶然大、也可能是偶然小;而多次随机抽样相当于给每个变量提供了多次"考试机会"。一个真正重要的变量,无论抽到哪一批样本,其回归系数的绝对值都会稳定地居于前列;一个靠运气突出重围的变量,则会在不同抽样下权重忽高忽低。
这种做法的本质是"交叉验证的思想下放到变量评估":不用单独留出验证集,而是通过反复重采样观察系数的稳定性。每轮抽样都得到一组回归系数,算法比较的是这些系数在不同抽样下的相对大小,而不是某一次的绝对数值。抽样比例这个参数的敏感性也在这里体现:比例太高,每轮模型几乎相同,系数波动小,等于白抽样;比例太低,样本子集不足以代表整体,系数方差过大,评估结果又不可靠。
2.2 回归系数绝对值:最朴素的变量贡献度量
有了PLS模型,每个变量都会得到一个回归系数。系数的绝对值越大,意味着该变量对预测结果的贡献越强;系数接近零的变量,则基本可以被视为噪声携带者。CARS把每个变量的回归系数绝对值作为其"权重",并在这个权重的基础上设计后续的随机抽样。
这里要特别强调一个细节:CARS用的不是VIP分数或相关系数,而是PLS回归系数。原因是回归系数天然地考虑了变量之间的联合作用——某个变量单独来看与目标值毫无线性关系,但在PLS的潜变量结构中,它可能与其他变量协同发挥作用,回归系数能捕捉这种协同贡献。VIP分数也能做到类似的事,但VIP是基于协方差的投影度量,而CARS选择回归系数的绝对值,核心动机是"可加性":多个变量组合的贡献大致可以按系数绝对值累加,这为后面的"按权重随机抽样"提供了天然的数学基础。
在实际计算中,权重计算非常廉价,核心逻辑只有几行:
import numpy as np from sklearn.cross_decomposition import PLSRegression def evaluate_variable_weight(X, y, sample_ratio=0.8, n_components=5): # 随机抽取样本子集,完成蒙特卡洛采样 n_samples = X.shape[0] sampled_idx = np.random.choice(n_samples, int(n_samples * sample_ratio), replace=False) # 用抽样样本建立 PLS 模型 pls = PLSRegression(n_components=n_components) pls.fit(X[sampled_idx], y[sampled_idx]) # 回归系数的绝对值作为该轮变量权重 coefficients = pls.coef_.ravel() weight = np.abs(coefficients) # 按权重降序排序,返回排序后的索引和权重 sorted_idx = np.argsort(weight)[::-1] return sorted_idx, weight这段代码把"蒙特卡洛采样 + 回归系数评估"两个步骤压缩在一个函数里。关键参数有三个:sample_ratio控制抽样比例,过大则每轮模型几乎相同,失去多样性;过小则模型不稳定,系数估计方差太大,一般取0.7到0.9之间。n_components是PLS潜变量数,它决定模型对数据结构的拟合程度,太小会欠拟合,太大会把噪声也拟合进去。返回值里的sorted_idx专门用于下一阶段的变量淘汰。
注意coef_的形状:PLSRegression的coef_在单因变量场景下是一个二维数组(形状为n_features行、1列),所以需要ravel()展平成一维。很多初写CARS实现的人会在这里取错维度,导致后续的索引对应不上原始变量,这是一个非常隐蔽的坑。
2.3 权重排序之后:次轮变量子集如何产生
每轮评估完权重后,并不是简单地"砍掉后半截"。CARS使用指数衰减函数确定本轮保留的变量比例,然后用自适应重加权采样按权重从保留变量里做放回式抽样,抽样结果作为下一轮的变量子集。
这里要区分两个概念:强制淘汰与随机抽样。强制淘汰是把当前轮次里"确定不重要"的变量直接删掉,这是硬性操作;随机抽样则是在剩余变量里按权重概率性地选取,权重大的变量有更高概率被选中,但权重小的变量也保留了一丝被选中的机会。这个"保留一丝机会"的设计恰恰是CARS与贪心算法的分水岭——贪心算法会无情地丢弃所有当前看起来不重要的变量,而CARS允许"眼下弱、但组合起来有用"的变量有存活的机会。
关于这个机制的数学细节,是下一章要展开的重点。
3. 指数衰减函数与自适应重加权采样:竞争淘汰的节奏控制
3.1 EDF的数学形式:为什么是幂函数衰减
CARS每轮要保留多少比例的变量,由指数衰减函数(EDF)决定。设初始变量总数为p,当前第i轮(从1开始计数),总运行轮数为N,则第i轮的变量保留率近似为:
- r_i = (p_min / p)^(i / N)
p_min是计划在最后一轮保留的最小变量数。这个形式变换一下就能看出指数衰减的本质:等式两边取对数,log(r_i) = (i/N)·log(p_min/p),保留率的对数随轮次线性下降,也就是说保留率本身是几何级数式的衰减。开头几轮淘汰速度快,越到后面淘汰速度越慢。
为什么不是线性衰减?线性衰减意味着每轮淘汰固定比例,这在变量数很多的光谱场景下会"抢跑"——前十几轮把大量变量快速砍光,后面想精细筛选时已经没有候选变量了。而指数衰减前半段快速粗筛、后半段慢速细选,正好匹配光谱变量"成百上千个变量里真正关键的只有几十个"的分布特征。
常见实现的EDF代码如下:
def compute_retained_ratio(current_round, total_rounds, n_variables, min_variables): """ 计算第 current_round 轮需要保留的变量比例 current_round: 从 1 开始计数的当前轮次 total_rounds: 总轮次 N n_variables: 初始变量总数 min_variables: 计划最终保留的最小变量数 """ # 指数衰减:保留率按轮次比例指数下降 ratio = (min_variables / n_variables) ** (current_round / total_rounds) return ratio # 每轮保留变量数 = 当前变量数 * ratio,向下取整 # 典型运行轨迹:从 1000 个变量出发,50 轮后计划保留约 30 个这里面的min_variables是个超参数,一般取2到10之间的值。它并不代表最终一定保留那么多变量,而是给EDF一个收敛锚点,让衰减曲线有末端约束。如果设得太小(比如1),衰减曲线过于陡峭,可能在第30轮就把变量数压到个位数,后面的轮次完全退化成"矮子里拔将军";如果设得偏大(比如50),衰减又太缓,前几轮几乎没什么淘汰效果,计算开销增大。
3.2 强制淘汰与自适应重加权:一只脚刹车、一只脚油门
EDF决定的是"保留多少个"——这是刹车;ARS决定的是"保留哪一些"——这是油门。两者必须配合起来才能让迭代正常推进。
强制淘汰是硬约束:即使某个变量之前一直权重很高,一旦EDF的保留变量数小于当前变量数,权重最低的那一截会被直接移除。这个步骤保证了变量子集规模的单调递减,是算法收敛的刹车。
自适应重加权采样是软操作:变量不是简单地按权重比例被抽中,而是要经过一个非均匀采样过程。具体做法是把保留下来的变量按权重归一化,每个变量的被抽中概率等于其权重占总权重的比例,然后从这个概率分布里做放回抽样,抽出的变量集合作为下一轮输入。
这一步是油门——它让权重高的变量在下一轮里被重复选中,从而增加其"出镜率";权重低的变量也有机会被抽到,避免了重要变量被一步错杀。用一句话概括:强制淘汰筛掉确定不行的,随机重加权筛掉概率上不行的。前者是规则驱动,后者是概率驱动。
3.3 权重归一化与相关性保持
还有一个经常被忽略的细节:CARS的随机抽样是放回式的。放回意味着同一变量可以在一次抽样中被抽到多次,这相当于变相提高了该变量在下一轮中的权重。叠加上权重归一化,高权重变量的影响力会被进一步放大。
很多人第一次看CARS会觉得奇怪:为什么要放回?放回不会导致变量重复吗?实际上,放回恰恰是CARS保护"协同变量组"的手段。在光谱数据里,一个有效变量往往与其附近波长的变量高度相关,它们是作为一个"模块"共同发挥作用的。如果抽样改成不放回,那么当某个模块里的多个变量被抽走后,模块的整体贡献会被削弱;而放回抽样允许模块里的关键成员被多次选中,保持模块在下一轮模型中的结构完整性。
理解到这一层,CARS和普通逐步回归的差别就更清晰了:逐步回归靠统计检验决定变量的去留,而CARS靠的是加权随机抽样带来的模块级选择压力。这种机制在变量高度共线性的光谱数据里特别有效,因为它不需要预先判断变量之间的相关结构,算法自己会在竞争中自发地保留那些互相支撑的变量组。
4. 从采样到收敛:CARS完整流程的机制拆解
4.1 运行阶段:N轮循环里发生了什么
把前两章的机制拼起来,CARS的完整运行流程可以拆成五个步骤,每轮迭代都走一遍:
- 从校正集样本中随机抽取一份子集(约80%)。
- 用该子集建立PLS模型,得到每个变量的回归系数。
- 取回归系数绝对值作为变量权重。
- 用EDF计算本轮变量保留数,强制淘汰权重最低的变量。
- 在保留变量中按权重做自适应重加权采样,生成下一轮的变量子集。
迭代N轮之后,算法会得到一个"轮次→变量子集"的轨迹。每一轮对应的变量子集都能重新建立PLS模型并用交叉验证评估预测能力。CARS最后做的事情是:用交叉验证去评估每一轮产生的变量子集所建立模型的RMSECV,选RMSECV最小的那一轮作为最优变量子集。
这里的关键是,最优变量子集往往不是最后那一轮。原因在于,EDF在最后几轮会把变量数压到很小的规模,此时已经出现欠拟合——变量子集漏掉了某些必要的协同信息。所以CARS的价值不是收敛到某个终点,而是在收敛路径上找到一个最佳停留点。
4.2 主循环实现与参数含义
下面给出一个结构完整的CARS主循环,包含EDF、强制淘汰和ARS三个核心步骤:
import numpy as np from sklearn.cross_decomposition import PLSRegression from sklearn.model_selection import cross_val_predict from sklearn.metrics import mean_squared_error def cars(X, y, total_rounds=50, sample_ratio=0.8, min_variables=3, n_components=5): n_samples, n_vars = X.shape current_idx = np.arange(n_vars) # 当前变量索引 results = [] # 每一轮的变量子集与RMSEcv for i in range(1, total_rounds + 1): # 1. 蒙特卡洛采样:随机抽样本 sampled_rows = np.random.choice(n_samples, int(n_samples * sample_ratio), replace=False) X_s = X[sampled_rows][:, current_idx] y_s = y[sampled_rows] # 2. 建立PLS模型,取回归系数绝对值作为权重 pls = PLSRegression(n_components=n_components) pls.fit(X_s, y_s) coef = np.abs(pls.coef_.ravel()) # 3. EDF计算保留比例,得到本轮的保留变量数 ratio = (min_variables / n_vars) ** (i / total_rounds) retain_count = max(int(len(coef) * ratio), 1) # 4. 强制淘汰:只保留权重最高的 retain_count 个变量 top_idx = np.argsort(coef)[::-1][:retain_count] selected = current_idx[top_idx] # 5. 自适应重加权采样:按权重放回抽样 weights = coef[top_idx] / (coef[top_idx].sum()) sampled = np.random.choice(selected, size=len(selected), p=weights, replace=True) current_idx = np.unique(sampled) # 去重后作为下一轮变量集 # 用当前变量子集做交叉验证评估 cv_pred = cross_val_predict(PLSRegression(n_components=n_components), X[:, current_idx], y, cv=5) rmsecv = mean_squared_error(y, cv_pred) ** 0.5 results.append((rmsecv, current_idx.copy())) # 选 RMSECV 最小的轮次作为最终变量子集 best = min(results, key=lambda r: r[0]) return best[1], best[0]几个需要注意的参数细节:sample_ratio是单轮采样的样本比例,对模型稳定性影响很大;total_rounds是总迭代次数,一般50到100之间;min_variables是EDF的末端锚点,不能单独看,要和total_rounds一起调节。第5步里np.unique去重是必要的,否则放回抽样可能让下一轮变量数膨胀,导致EDF无法按预期控制规模。
这段代码也暴露了CARS的一个典型短板:每一轮都要重新训练PLS并进行交叉验证,计算量不小。对于光谱数据动辄几千个变量、几百个样本来说,跑50轮通常还好;如果数据规模更大,建议用并行方式将各轮的交叉验证分散到多个计算单元上。
4.3 交叉验证与最终选择:轮次轨迹有什么价值
CARS最终的输出并不是一个普通的变量列表,而是一个带评估记录的轨迹。这个轨迹信息非常有用:
第一,它可以告诉你模型从过拟合到欠拟合的变化过程。最早的几轮变量最多、模型最容易过拟合;中间的轮次变量数适中,RMSECV往往最低;最后几轮变量太少,RMSECV重新上升。画出"轮次→RMSECV"的曲线,你会非常直观地看到什么规模的变量子集对这组数据是"甜蜜点"。
第二,它可以帮你识别不稳定变量。如果一个变量在多轮次中都出现在变量子集里,说明它是稳定重要的;如果某变量只在个别轮次出现且其后迅速消失,说明它权重不高、可有可无。实践中,运行多次CARS,统计每个变量被选中的频率,挑选出现频率高的变量组成最终集合,是一种比单次运行更稳健的做法。
第三,轮次曲线的形状还能用来判断潜变量数n_components设得是否合理。如果曲线非常平坦、所有轮次的RMSECV都差不多,说明潜变量数选择不当,模型没有充分利用变量信息;如果曲线在中间轮次出现明显低谷,则说明潜变量数设置是合适的。
5. CARS实践避坑:变量筛选中的常见问题与排查
5.1 每次运行选出的变量都不一样
现象:同一份数据、同样的参数,连续跑三次CARS,三次选出的变量子集重合度不到一半。
原因:CARS每一轮都做蒙特卡洛采样,随机性从第一轮就开始累积。如果total_rounds取得偏小(比如20轮),随机偏差还没有被充分平均掉,最终结果会高度依赖初始抽样的"运气"。
解决:把total_rounds提到50以上,并多次运行取变量出现频率。具体做法是先跑10次CARS,统计每个变量被选中的次数,再按出现频率排序选出高频变量。留意一个边界情况:如果样本量本来就只有40、50个,抽样比例0.8会让每轮仅抽30多个样本,此时建议降低total_rounds、提高sample_ratio到0.85以上,避免样本代表性不足。
5.2 先标准化还是先跑CARS
现象:CARS选出的变量集中在中高频波长段,而低频段几乎没有变量被选中,模型效果却很差。
原因:不同波段的信号强度差异很大。CARS的权重基于PLS回归系数绝对值,如果原始光谱没有做过标准化,数值大的波段天然会获得更高权重,算法倾向保留"数值大"的变量而不是"贡献大"的变量。
解决:先对光谱数据做均值中心化或方差标准化,再运行CARS。均值中心化通常就够了,因为PLS本身对数据的列中心化有天然需求;如果数据量纲差异极端,再叠加方差标准化。经验上,标准化的操作必须在划分校正集和测试集之前统一完成,避免数据泄漏。
5.3 校正集和测试集划分时机不对
现象:CARS在训练数据上选出的变量表现很好,但在独立测试集上误差翻倍。
原因:如果先把全部样本混在一起做CARS,再划分校正集和测试集,变量选择过程已经把测试集的信息泄漏进了模型的选变量阶段。这属于典型的"后选择偏差",变量选择时的交叉验证结果虚高,真实泛化能力远低于预期。
解决:严格按照"先划分、后筛选"的流程:先把数据按7:3或8:2切成校正集和测试集,只在校正集上运行CARS,选出的变量固定后,再用相同的变量索引去提取测试集特征。CARS的变量选择结果不能因为加入了测试集样本而重新计算。
5.4 RMSECV最小的轮次太多,无法区分
现象:RMSECV曲线在很长一段范围内非常接近,最小值前后的轮次波动差别很小,难以判断哪一轮是真正最优。
原因:变量子集之间的预测能力差异可能很小,交叉验证本身的随机波动就已经掩盖了差异。这种情况在共线性严重、变量数接近的情况下很常见。
解决:优先选变量数更少的那一轮,因为模型更简洁、更容易部署和解释。另外可以配合变量出现频率,选同时满足"RMSECV接近最优"和"变量出现频率高"的子集。
提示:CARS选出的变量子集必须经过再验证。用选出的变量重新建立PLS模型,在测试集上计算RMSEP,和全变量模型对比,确认变量筛选确实带来了泛化性能提升,才算走完完整流程。
5.5 数据量太小导致PLS拟合失败
现象:某轮迭代时PLS模型无法收敛,报出线性代数相关的错误。
原因:蒙特卡洛抽样后参与建模的样本数可能少于潜变量数,或在某些变量组合下设计矩阵接近奇异,导致矩阵求逆失败。
解决:检查n_components是否小于抽样样本数的三分之一;提前对抽样后的矩阵做条件数检查,如果条件数过大,跳过该轮或重新抽样。更根本的做法是在数据预处理阶段先做简单的无信息变量剔除,把明显是纯噪声的波长提前删掉,减少CARS在高危区域挣扎的概率。
6. 设计取舍:为什么CARS选择"竞争"而不是"贪心"
把CARS和同类算法摆在一起,会看到一条清晰的设计取舍线。逐步回归、向前选择这类贪心算法试图在每一步做出"当前最优"的决定,每步精准砍掉一个或几个变量,参数少、可解释性强,但它们对变量的共线性极度敏感:两个相关性极高的变量,贪心算法会先选一个,然后再也无法公正评估另一个的独立贡献。CARS用随机性和竞争绕开了这个问题——它不在每一步做最优决策,而是让多个变量在反复竞争中自发分层,重要变量自然留在高频区,冗余变量被随机淘汰。
这个选择是有代价的:CARS的随机性导致单次运行结果不可完全复现,必须靠多次运行或调大N来换取稳定。相比贪心算法的一次性确定输出,CARS像是一种"统计式的变量选择",它的结果本质上是一个概率分布,而不是一个唯一答案。我个人的使用习惯是:把CARS当作变量预筛器,而非最终决策器。先用CARS把变量从上千压到几十,再用简单的手工推理或逐步回归从几十里挑出最终的十几二十个。这样既利用了CARS对共线性的容忍,又保留了贪心算法在最后阶段的确定性。
另一个值得想的设计点是EDF的"强制性"。为什么不让权重直接决定去留,而要额外加一个指数衰减的外力?因为纯按权重随机抽样,变量的淘汰速度会很慢,而且方差大;EDF给了整个迭代一个时间表,让变量数在第N轮之前收敛到用户预设的尺度。这个外力看似粗暴,其实是对随机过程的一种正则化——它保证了收敛速度,也让"何时停止"这个问题摆脱了对权重阈值的主观设定。
从那以后,我每次跑CARS都会做三件事:先画RMSECV曲线看收敛节奏,多跑几次统计频率,再用最终变量子集做一次独立的测试集验证。这套流程每次都能帮我把变量从混乱的物理信号里捞出来。希望帮到你。
本文还有配套的精品资源,点击获取