如何对PCA执行交叉验证以确定主成分的数量?


12

我正在尝试编写自己的用于主成分分析的函数PCA(当然已经编写了很多东西,但我只是想自己实现一些东西)。我遇到的主要问题是交叉验证步骤和计算预测的平方和(PRESS)。我使用哪种交叉验证都没有关系,这主要是关于背后的理论的问题,但请考虑留一法交叉验证(LOOCV)。从理论上我发现,要执行LOOCV,您需要:

  1. 删除对象
  2. 扩展其余部分
  3. 使用一些组件执行PCA
  4. 根据(2)中获得的参数缩放删除的对象
  5. 根据PCA模型预测对象
  6. 计算该对象的压力
  7. 对其他对象重新执行相同的算法
  8. 汇总所有PRESS值
  9. 利润

因为我是该领域的新手,所以为了确定我是对的,我将结果与我所拥有的某些软件的输出进行比较(同样为了编写一些代码,我遵循该软件中的说明进行操作)。计算残差平方和,我得到完全相同的结果,但是计算PRESS是个问题。[R2

您能否告诉我在交叉验证步骤中实施的方法是否正确:

case 'loocv'

% # n - number of objects
% # p - number of variables
% # vComponents - the number of components used in CV
dataSets = divideData(n,n); 
         % # it is just a variable responsible for creating datasets for CV 
         % #  (for LOOCV datasets will be equal to [1, 2, 3, ... , n]);'
tempPRESS = zeros(n,vComponents);

for j = 1:n
  Xmodel1 = X; % # X - n x p original matrix
  Xmodel1(dataSets{j},:) = []; % # delete the object to be predicted
  [Xmodel1,Xmodel1shift,Xmodel1div] = skScale(Xmodel1, 'Center', vCenter, 
                                              'Scaling', vScaling); 
          % # scale the data and extract the shift and scaling factor
  Xmodel2 = X(dataSets{j},:); % # the object to be predicted
  Xmodel2 = bsxfun(@minus,Xmodel2,Xmodel1shift); % # shift and scale the object
  Xmodel2 = bsxfun(@rdivide,Xmodel2,Xmodel1div);
  [Xscores2,Xloadings2] = myNipals(Xmodel1,0.00000001,vComponents); 
          % # the way to calculate the scores and loadings
                % # Xscores2 - n x vComponents matrix
                % # Xloadings2 - vComponents x p matrix
  for i = 1:vComponents
    tempPRESS(j,i) = sum(sum((Xmodel2* ...
       (eye(p) - transpose(Xloadings2(1:i,:))*Xloadings2(1:i,:))).^2));
  end
end
PRESS = sum(tempPRESS,1);

在软件(PLS_Toolbox)中,它的工作方式如下:

for i = 1:vComponents
    tempPCA = eye(p) - transpose(Xloadings2(1:i,:))*Xloadings2(1:i,:);
    for kk = 1:p
        tempRepmat(:,kk) = -(1/tempPCA(kk,kk))*tempPCA(:,kk);
          % # this I do not understand
        tempRepmat(kk,kk) = -1; 
          % # here is some normalization that I do not get
    end 
    tempPRESS(j,i) = sum(sum((Xmodel2*tempRepmat).^2)); 
end

因此,他们使用此tempRepmat变量进行了一些其他的归一化处理:我发现的唯一原因是,他们将LOOCV应用于健壮的PCA。不幸的是,支持团队不想回答我的问题,因为我只有他们软件的演示版。


进一步检查我是否理解其他规范化代码段:tempRepmat(kk,kk) = -1line 的作用是什么?上一行是否已经确保tempRepmat(kk,kk)等于-1?还有,为什么要减少?该错误仍然会被平方,所以我是否正确理解,如果消除了这些缺点,什么都不会改变?
变形虫说恢复莫妮卡

我过去检查过它,什么都不会改变。没错 我发现与健壮的PCA实现仅存在一些相似之处,因为在这种实现中(在对所有内容进行求和之前)每个计算出的PRESS值都有自己的权重。
Kirill 2014年

我正在寻找与答案中提供的MATLAB代码等效的R代码,并提出了悬赏计划。
AIM_BLB

3
@CSA,要求代码不在这里(甚至可能通过注释和赏金)。您应该能够在Stack Overflow上询问:您可以复制代码,在此处引用带有链接的源代码,并寻求翻译。我相信所有这些都将在那里讨论。
gung-恢复莫妮卡

Answers:


21

您正在做的事情是错误的:像这样为PCA计算PRESS毫无意义!具体来说,问题出在您的第5步。


PCA PRESS的幼稚方法

让所述数据集包括在点d维空间:X [R dñd。要为一个测试数据点计算重构误差 X ,则在训练集执行PCA X - 与该点排除,取一定数目 ķ主轴的作为列 ù - ,和找到重建误差X - X 2 = X -X一世[Rd一世=1个ñX一世X-一世ķü-一世。PRESS等于所有测试样本i的总和,因此合理的方程式似乎是:PRESS= Ñ Σ= 1 | | X - ù - [ ù - ] XX一世-X^一世2=X一世-ü-一世[ü-一世]X一世2一世

P[RË小号小号=一世=1个ñX一世-ü-一世[ü-一世]X一世2

为了简单起见,我忽略了此处居中和缩放的问题。

天真的方法是错误的

X一世X^一世

ÿ一世-ÿ^一世2ÿ^一世ÿ一世

ķd

正确的方法

XĴ一世X一世http://alexhwilliams.info/itsneuronalblog/2018/02/26/crossval/进行了很好的讨论和Python实现(具有缺失值的PCA通过交替的最小二乘法实现)。

X一世X一世

P[RË小号小号PC一种=一世=1个ñĴ=1个d|XĴ一世-[ü-一世[ü-Ĵ-一世]+X-Ĵ一世]Ĵ|2

在这里考虑内循环。我们忽略了一个点的“凸起”(在最小二乘的意义上)的第坐标X - Ĵ到子空间跨越由ù - 。为了计算它,找到一个点ž其中ù - - Ĵü - X一世ķü-一世XĴ一世X-Ĵ一世[Rd-1个X^Ĵ一世ĴX-Ĵ一世ü-一世ž^[RķX-Ĵ一世ž^=[ü-Ĵ-一世]+X-Ĵ一世[Rķü-Ĵ-一世ü-一世Ĵ[]+ž^ü-一世[ü-Ĵ-一世]+X-Ĵ一世Ĵ[]Ĵ

正确方法的近似值

我不太了解PLS_Toolbox中使用的其他规范化,但是这是一种朝着同一方向发展的方法。

还有另一种映射方式 X-Ĵ一世ž^一种pp[RØX=[ü-Ĵ-一世]X-Ĵ一世,即简单地采用转置而不是伪逆。换句话说,根本不计入测试的尺寸,并且相应的权重也被简单地剔除掉。我认为这应该不太准确,但通常可以接受。好处是,现在可以按以下方式对所得公式进行矢量化处理(我省略了计算):

P[RË小号小号PC一种一种pp[RØX=一世=1个ñĴ=1个d|XĴ一世-[ü-一世[ü-Ĵ-一世]X-Ĵ一世]Ĵ|2=一世=1个ñ一世-üü+d一世一种G{üü}X一世2

ü-一世üd一世一种G{}üü

更新(2018年2月):在上面,我将一个过程称为“正确”,另一个称为“近似”,但我现在不确定这是否有意义。两种程序都有意义,我认为两者都不是更正确的。我非常喜欢“近似”过程具有一个更简单的公式。另外,我还记得我有一些数据集,其中“近似”过程产生的结果看起来更有意义。不幸的是,我不再记得细节了。


例子

以下是这些方法与两个著名数据集的比较:虹膜数据集和葡萄酒数据集。请注意,朴素方法会产生单调递减的曲线,而其他两种方法会产生最小的曲线。还要注意,在虹膜情况下,近似方法建议以1 PC作为最佳数,而伪逆方法建议为2 PC。(然后查看Iris数据集的任何PCA散点图,似乎两个第一台PC都携带一些信号。)在酒的情况下,伪逆方法显然指向3个PC,而近似方法无法确定3到5之间。

在此处输入图片说明


Matlab代码执行交叉验证并绘制结果

function pca_loocv(X)

%// loop over data points 
for n=1:size(X,1)
    Xtrain = X([1:n-1 n+1:end],:);
    mu = mean(Xtrain);
    Xtrain = bsxfun(@minus, Xtrain, mu);
    [~,~,V] = svd(Xtrain, 'econ');
    Xtest = X(n,:);
    Xtest = bsxfun(@minus, Xtest, mu);

    %// loop over the number of PCs
    for j=1:min(size(V,2),25)
        P = V(:,1:j)*V(:,1:j)';        %//'
        err1 = Xtest * (eye(size(P)) - P);
        err2 = Xtest * (eye(size(P)) - P + diag(diag(P)));
        for k=1:size(Xtest,2)
            proj = Xtest(:,[1:k-1 k+1:end])*pinv(V([1:k-1 k+1:end],1:j))'*V(:,1:j)'; 
            err3(k) = Xtest(k) - proj(k);
        end

        error1(n,j) = sum(err1(:).^2);
        error2(n,j) = sum(err2(:).^2);
        error3(n,j) = sum(err3(:).^2);
    end    
end

error1 = sum(error1);
error2 = sum(error2);
error3 = sum(error3);
%// plotting code
figure
hold on
plot(error1, 'k.--')
plot(error2, 'r.-')
plot(error3, 'b.-')
legend({'Naive method', 'Approximate method', 'Pseudoinverse method'}, ...
    'Location', 'NorthWest')
legend boxoff
set(gca, 'XTick', 1:length(error1))
set(gca, 'YTick', [])
xlabel('Number of PCs')
ylabel('Cross-validation error')

谢谢您的回答!我知道那张纸。然后,我应用了此处描述的逐行交叉验证(似乎与我提供的代码完全对应)。我将其与PLS_Toolbox软件进行了比较,但是它们在LOOCV中只有一行我真的不懂的代码(我在原始问题中写道)。
Kirill 2014年

是的,他们称其为“逐行交叉验证”,并且您的实现似乎还不错,但是请注意,这是执行交叉验证的一种不好方法(正如Bro等人所述,并在经验中得到了证明),基本上,您应该永远不要使用它!关于您不理解的代码行-您要更新问题吗?不确定您指的是什么。
变形虫说恢复莫妮卡

事实是,这种实现似乎具有达到CV最小值的能力。
Kirill 2014年

X^-X

ķi

1

要为@amoeba的漂亮答案添加一个更一般的观点:

有监督模型和无监督模型之间的一个实用且至关重要的区别是,对于无监督模型,您需要更加认真地思考您认为等同或不等同的东西。

ÿ^ÿ^ÿ

为了构造有意义的性能度量,您需要考虑模型的哪些自由度对您的应用程序没有意义,哪些自由度没有意义。在某种类似于Procrustes的旋转/翻转之后,这可能会导致对分数产生压力。

按x我的猜测是(我现在没有时间找出他们的2行代码的作用-但也许您可以单步执行并看看?):

为了获得可用于确定良好模型复杂性的度量,该度量通常会增加拟合优度,直到达到满秩模型为止,您需要对过于复杂的模型进行惩罚。反过来,这意味着这种惩罚是a)至关重要的,并且b)调整惩罚将调整所选的复杂性。


旁注:我想补充一点,在这种类型的自动化模型复杂度优化方面,我会非常小心。以我的经验,这些算法中的许多算法仅产生伪客观性,并且常常以仅对某些类型的数据良好运行为代价。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.