返回

PLS 回归结果负数? Scikit-learn 非负约束实战

python

让 PLS 回归结果告别负数:如何在 Scikit-learn 中强制非负约束

搞数据分析,特别是处理像光谱信号这种叠加数据时,偏最小二乘回归(Partial Least Squares Regression, PLS)是个挺好用的工具。它可以处理特征比样本多、特征之间还可能共线性的情况。但是,用 Scikit-learn 里的 PLSRegression 时,有时会碰到个头疼的问题:模型预测出来的某些组分值是负数。在很多实际场景里,比如化学浓度、信号强度,负值是没道理的,不符合物理或化学定律。

那有没有办法让 PLSRegression 老实点,只输出正数或者零呢?

一、遇到问题:PLS 回わりにくわえる? (PLS 回归出现负值?)

具体来说,你可能正用 sklearn.cross_decomposition.PLSRegression 分析你的光谱数据,这些数据由几个已知成分的光谱叠加而成。目标是估计每个成分的贡献(比如浓度或者相对强度)。你期望得到的结果,逻辑上讲,应该是大于等于零的。

可跑完模型一看,pls.predict(X_test) 给出的结果里,某些列(对应某个成分)居然出现了负数。特别是那些信号本身比较弱的成分,更容易出现这种情况。这就让人很困惑,模型是不是哪里没搞对?

import numpy as np
from sklearn.cross_decomposition import PLSRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# 假设我们有一些模拟的光谱数据
# X 是 (样本数, 光谱点数)
# Y 是 (样本数, 组分数),我们希望 Y >= 0
n_samples, n_features, n_components_true = 100, 500, 3
rng = np.random.RandomState(0)
X = rng.randn(n_samples, n_features)
# 假设真实的组分系数 (大部分是正的)
true_coefs = rng.rand(n_features, n_components_true) * 2 - 0.5 # 可能有少量负值代表噪声?
true_coefs[true_coefs < 0] = 0 # 强制大部分非负

# 模拟真实的 Y (目标组分含量),确保非负
Y_true = np.dot(X, true_coefs) + rng.randn(n_samples, n_components_true) * 0.5
Y_true[Y_true < 0] = 0 # 确保物理意义上的非负性

# 分割数据集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y_true, test_size=0.3, random_state=42)

# 训练一个标准的 PLS 模型
n_pls_components = 10 # 假设我们选择了 10 个 PLS 成分
pls = PLSRegression(n_components=n_pls_components)
pls.fit(X_train, Y_train)

# 进行预测
Y_pred = pls.predict(X_test)

# 检查预测结果中是否有负值
print(f"预测结果 Y_pred 的最小值: {Y_pred.min()}")
if Y_pred.min() < 0:
    print("哎呀,PLS 预测结果里出现了负值!")

# (输出可能类似):
# 预测结果 Y_pred 的最小值: -0.8512...
# 哎呀,PLS 预测结果里出现了负值!

看,问题复现了。接下来分析下为啥会这样。

二、为啥 PLS 会搞出负值?

PLS 本身的设计目标是通过寻找 X(输入特征)和 Y(目标变量)之间的潜在变量(Latent Variables, LVs),来最大化它们之间的协方差,同时用这些潜在变量来预测 Y。这个过程本质上还是基于最小二乘法的思想。

标准的最小二乘法,包括 PLS 里的回归步骤,是为了让预测误差的平方和最小。数学优化过程中,它并不关心预测值本身是不是符合某种物理约束(比如非负)。只要某个负的系数或预测值能让整体误差更小,算法就会接受它。

这在以下情况更容易发生:

  1. 信号弱或噪声大: 对于本身贡献就很小的组分,噪声的干扰或者模型为了拟合其他强信号而产生的微小调整,都可能使其估计值掉到零以下。
  2. 模型复杂度(成分数 n_components)选择不当: 如果选择的 PLS 成分数过多,模型可能会过拟合,试图解释数据中的噪声,也可能导致奇怪的负值系数或预测。
  3. 数据本身存在问题: 比如基线漂移、异常值等没有很好处理,也会干扰模型的学习。

明白了原因,就好对症下药了。可惜的是,Scikit-learn 的 PLSRegression 实现里,并没有直接提供像 positive=True 这样的参数来强制非负约束。所以,我们需要想点别的招。

三、解决方案:给 PLS 上个“紧箍咒”

既然官方没给直路,我们就得绕点弯路或者换个思路。下面是几种可行的办法。

3.1 简单粗暴?后处理截断

这是最直接、最容易想到的方法:先用标准的 PLS 模型进行预测,然后检查预测结果,把所有小于零的值直接改成零。

  • 原理:
    简单来说,就是模型照常跑,结果出来后加一道“过滤器”。不改变 PLS 算法本身,只修正最终输出。

  • 操作步骤:
    接上文代码,拿到 Y_pred 后:

    # 对预测结果进行截断
    Y_pred_clipped = np.clip(Y_pred, 0, None) # 将小于 0 的值设为 0,上限不限制
    
    print(f"截断后 Y_pred_clipped 的最小值: {Y_pred_clipped.min()}")
    # (输出):
    # 截断后 Y_pred_clipped 的最小值: 0.0
    
  • 优缺点与建议:

    • 优点: 实现极其简单,一行代码搞定。
    • 缺点: 这种做法在数学上可能不太站得住脚。它破坏了原始 PLS 最小二乘优化得到的结果。可能导致最终结果并非在非负约束下的最优解。如果负值很大或者很多,截断后的结果可能跟真实情况偏差较大。
    • 建议: 可以作为一种快速尝试或最后的补救措施。如果负值出现的很少且绝对值很小,这种方法影响可能不大。但如果问题严重,或者对模型的理论严谨性要求高,最好考虑下面的方法。

3.2 曲线救国:PLS 降维 + 非负线性回归

这个方法稍微复杂点,但更符合约束优化的思想。核心思路是:利用 PLS 做它最擅长的事——降维和提取与 Y 相关的特征,然后在这个降维后的空间里,使用一个本身就支持非负约束的回归模型来做最后的预测。

Scikit-learn 里的 LinearRegression 提供了一个 positive=True 参数,可以实现非负最小二乘(Non-Negative Least Squares, NNLS)。我们可以把它和 PLS 的变换(transform)功能结合起来。

  • 原理:

    1. 用训练数据 (X_train, Y_train) 拟合一个 PLS 模型。这一步的目的是找到 X 空间中的一组潜在变量(也叫 X-scores),这些变量能最好地解释 X 和 Y 的协方差。
    2. 使用拟合好的 PLS 模型,将原始的训练数据 X_train 变换到这个低维的潜在变量空间,得到 X_train_scores
    3. 现在,我们把问题转化为:用 X_train_scores 作为新的特征,来预测 Y_train。在这个阶段,我们使用带有非负约束的线性回归模型 (LinearRegression(positive=True)) 进行拟合。这个模型会确保找到的系数都是非负的,进而使得基于 scores 的预测也倾向于非负(如果 Y 本身是非负的)。
    4. 对于新的测试数据 X_test,先用同一个 PLS 模型将其变换到潜在变量空间得到 X_test_scores,然后用训练好的非负线性回归模型基于 X_test_scores 进行预测。
  • 代码示例:

    from sklearn.linear_model import LinearRegression
    
    # 1. 训练 PLS 模型 (与之前一样)
    n_pls_components = 10 # 保持 PLS 成分数不变或重新选择
    pls = PLSRegression(n_components=n_pls_components)
    pls.fit(X_train, Y_train)
    
    # 2. 将训练数据 X_train 变换到 PLS 潜在变量空间
    X_train_scores = pls.transform(X_train)
    
    # 3. 训练非负线性回归模型
    # 注意:如果 Y 是多维的 (多个组分), LinearRegression 默认会为每个输出拟合一个模型
    nnls_regressor = LinearRegression(positive=True)
    nnls_regressor.fit(X_train_scores, Y_train)
    
    # 4. 对测试数据进行预测
    #   a. 先将 X_test 变换到 PLS 空间
    X_test_scores = pls.transform(X_test)
    #   b. 然后用非负线性回归模型预测
    Y_pred_nnls = nnls_regressor.predict(X_test_scores)
    
    print(f"PLS + NNLS 预测结果 Y_pred_nnls 的最小值: {Y_pred_nnls.min()}")
    
    # 检查非负线性回归的系数是否都是非负的
    print(f"NNLS 回归系数的最小值: {nnls_regressor.coef_.min()}")
    
    # (输出可能类似):
    # PLS + NNLS 预测结果 Y_pred_nnls 的最小值: 0.0
    # NNLS 回归系数的最小值: 0.0
    

    可以看到,预测结果和回归系数都满足了非负的条件。

  • 优缺点与建议:

    • 优点: 这种方法在理论上更合理。它将 PLS 的降维能力和非负约束的回归分离开,分别处理。最终的预测是在强制非负的条件下优化得到的(在 PLS 变换后的空间内)。
    • 缺点: 实现步骤稍微多一点。需要理解 PLS 的 fittransform 的区别。模型的解释性可能稍有变化,因为最后的回归系数是针对 PLS scores 的,而不是原始特征。
    • 建议: 这是比较推荐的方法,尤其是当你需要一个数学上更说得通的、带有非负约束的模型时。
  • 进阶使用技巧:

    • PLS 成分数选择(n_components): 无论是直接用 PLS 还是结合 NNLS,PLS 成分数的选择都非常关键。这个数太小可能欠拟合,太大可能过拟合(即使后面加了 NNLS)。通常需要通过交叉验证(Cross-Validation)来确定一个最优的 n_components。你可以评估不同 n_components 下,使用“PLS+NNLS”整个流程的预测性能(例如,均方误差 MSE),选择表现最好的那个。
    • 多目标回归的处理: 当 Y 有多个维度(比如同时预测多个组分浓度)时,LinearRegression(positive=True) 能够很好地处理,它会为每个 Y 的维度独立拟合一组非负系数。
    • 数据标准化: PLS 对数据的尺度(scale)比较敏感。在应用 PLS 之前,通常需要对 X 和 Y 进行标准化处理(例如,中心化、缩放到单位方差)。Scikit-learn 的 PLSRegression 默认会进行中心化和标准化(可以通过 scale=False 关闭)。当你手动组合 pls.transformLinearRegression 时,要确保数据处理方式的一致性。pls.transform 会使用 pls.fit 时学到的均值和标准差来处理新数据。

四、选哪个方案?

一般情况下,推荐使用“PLS 降维 + 非负线性回归” (方法 3.2)。它提供了一种结构化的方式来引入非负约束,结果更可靠,也更容易从模型验证的角度(比如交叉验证选择参数)来评估。

后处理截断(方法 3.1)可以作为一种快速检查或者权宜之计。如果你的负值问题非常轻微(比如接近于零的微小负值,并且很少出现),用它可能也够了。但心里要有数,它可能不是“最优”的非负解。

五、注意事项与一点提醒

  1. 数据预处理不能忘: 不管用哪种方法,PLS 前的数据预处理(如基线校正、标准化等)都非常重要,能显著影响模型效果。特别是标准化 (scale=True in PLSRegression or using StandardScaler),记得要一致地应用到训练和测试数据上。
  2. n_components 的选择是门艺术: 如前所述,用交叉验证等方法仔细选择 PLS 的成分数。一个好的 n_components 是模型成功的关键。
  3. 结果的物理解释: 即使模型给出了非负结果,也要结合你的专业知识判断结果是否合理。比如,某个组分的预测值是不是异常地高或低?模型的预测性能(RMSE, R^2 等指标)是否达标?
  4. 有没有更对口的模型? 如果你的问题本质上更符合“非负矩阵分解”(Non-negative Matrix Factorization, NMF)的应用场景(例如,试图直接从混合信号中分解出非负的源信号和它们的贡献),可以考虑探索 sklearn.decomposition.NMF。NMF 本身就带有非负约束,但它的原理和目标与 PLS 不同,需要看哪个更适合你的具体问题。

处理模型输出不符合物理约束的情况是数据分析中常遇到的挑战。希望上面介绍的方法,特别是结合 PLS 变换和非负线性回归的策略,能帮你有效地解决 PLS 回归中出现的负值预测问题。