Як виконати перехресну перевірку PCA для визначення кількості основних компонентів?


12

Я намагаюся написати власну функцію для аналізу основних компонентів, PCA (звичайно, там вже багато написано, але мені просто цікаво реалізувати речі власноруч). Основна проблема, з якою я зіткнулася, - це крос перехресної перевірки та обчислення прогнозованої суми квадратів (PRESS). Не має значення, яку перехресну валідацію я використовую, це питання головним чином щодо теорії, яка стоїть позаду, але розглянемо перехресну валідацію "відключення" (LOOCV). З теорії я з'ясував, що для виконання LOOCV вам потрібно:

  1. видалити об’єкт
  2. масштабуйте решту
  3. виконати PCA з деякою кількістю компонентів
  4. масштабування видаленого об'єкта за параметрами, отриманими в (2)
  5. прогнозувати об’єкт за моделлю PCA
  6. обчислити PRESS для цього об’єкта
  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) = -1лінії? Чи не попередній рядок вже гарантує, що tempRepmat(kk,kk)дорівнює -1? Також, чому мінуси? Помилка все одно буде зафіксована, тому я правильно розумію, що якщо мінуси будуть зняті, нічого не зміниться?
амеба

Я перевіряв це в минулому і нічого не зміниться. Це правильно. Я знайшов лише деякі паралелі з надійними реалізаціями PCA, оскільки кожне обчислене значення PRESS у такій реалізації (перш ніж підсумовувати все) має свою вагу.
Кирило

Я шукаю код R, еквівалентний коду MATLAB, наведеному у відповіді, і виставив щедрості.
AIM_BLB

3
@CSA, запит коду тут поза темою (навіть, імовірно, через коментарі та приємності). Ви повинні мати можливість запитати про це в Stack Overflow : ви можете скопіювати код, посилайте сюди джерело w / посилання та попросити переклад. Я вірю в усе, що там буде на тему.
gung - Відновити Моніку

Відповіді:


21

Те, що ви робите, не так: не має сенсу обчислювати PRESS для PCA так! Зокрема, проблема полягає у вашому кроці №5.


Наївний підхід до PRESS для PCA

Нехай набір даних складається з точок у -вимірному просторі: . Щоб обчислити помилку відновлення для однієї точки тестування даних , ви виконуєте PCA на навчальному наборі без цього пункту виключається певна кількість основних осей як стовпці , і знайдіть помилку відновлення як . Тоді PRESS дорівнює сумі для всіх тестових зразківdndx(i)Rd,i=1nx(i)X(i)kU(i)x(i)x^(i)2=x(i)U(i)[U(i)]x(i)2i, тож розумним рівнянням здається:

PRESS=?i=1nx(i)U(i)[U(i)]x(i)2.

Для простоти я ігнорую питання центрування та масштабування тут.

Наївний підхід неправильний

Проблема вище полягає в тому, що ми використовуємо для обчислення прогнозу , і це дуже погана річ.x(i)x^(i)

Зверніть увагу на вирішальну відмінність випадку регресії, коли формула помилки відновлення в основному однакова , але прогнозування обчислюється за допомогою змінних предиктора та не використовує . Це неможливо в PCA, оскільки в PCA немає залежних і незалежних змінних: всі змінні обробляються разом.y(i)y^(i)2y^(i)y(i)

На практиці це означає, що PRESS, як було обчислено вище, може зменшуватися зі збільшенням кількості компонентів і ніколи не досягати мінімуму. Що привело б до думки, що всі компоненти є важливими. Або, можливо, в деяких випадках це досягає мінімуму, але все ще має тенденцію перевищувати та завищувати оптимальні розміри.kd

Правильний підхід

Існує кілька можливих підходів, див. Бро та ін. (2008) Перехресна перевірка компонентних моделей: критичний погляд на сучасні методи огляду та порівняння. Один із підходів полягає в тому, щоб залишити один вимір однієї точки даних за раз (тобто замість ), щоб дані тренінгу стали матрицею з одним відсутнім значенням , а потім передбачити ("імпультувати") це відсутнє значення за допомогою PCA. (Звичайно, можна випадковим чином витримати більшу частину елементів матриці, наприклад, 10%). Проблема полягає в тому, що обчислення PCA з відсутніми значеннями може бути обчислювально досить повільним (воно покладається на алгоритм EM), але його тут потрібно багато разів повторювати. Оновлення: див. Http://alexhwilliams.info/itsneuronalblog/2018/02/26/crossval/xj(i)x(i) для приємного обговорення та реалізації Python (PCA з відсутніми значеннями реалізується через чергування найменших квадратів).

Підхід, який я вважав набагато практичнішим, полягає в тому, щоб залишити одну точку даних за один раз, обчислити PCA на навчальних даних (точно так, як вище), але потім перевести цикл на розміри , залиште їх по черзі та обчисліть помилку відновлення за допомогою решти. На початку це може бути досить заплутаним, і формули, як правило, стають досить безладними, але реалізація є досить простою. Дозвольте спочатку дати формулу (дещо страшно), а потім коротко пояснити:x(i)x(i)

PRESSPCA=i=1nj=1d|xj(i)[U(i)[Uj(i)]+xj(i)]j|2.

Розглянемо тут внутрішню петлю. Ми залишили одну точку і обчислили основні компоненти на навчальних даних . Тепер ми зберігаємо кожне значення як тест і використовуємо інші розміри для виконання прогнозування . Прогнозування - -та координата "проекції" (у значенні найменших квадратів) на підпростір. від . Для його обчислення знайдіть точку в просторі ПК яка найближча до k U ( - i ) x ( i ) j x ( i ) - j i ) - j ] + x ( i ) - j R k U (x(i)kU(i)xj(i)xj(i)Rd1x^j(i)jxj(i)U(i)z^Rkxj(i) шляхом обчислення де є з -м рядом вигнали, і означає псевдоінверс. Тепер поверніть карту назад у вихідний простір: і візьмемо його ю координату . z^=[Uj(i)]+xj(i)RkUj(i)U(i)j[]+z^U(i)[Uj(i)]+xj(i)j[]j

Наближення до правильного підходу

Я не зовсім розумію додаткову нормалізацію, що використовується в PLS_Toolbox, але ось один підхід, який йде в тому ж напрямку.

Є ще один спосіб відобразити на простір основних компонентів: , тобто просто перейміть замість псевдо-зворотного. Іншими словами, розмір, який залишається для тестування, взагалі не рахується, а відповідні ваги також просто витісняються. Я думаю, що це має бути менш точним, але часто може бути прийнятним. Хороша річ у тому, що отриману формулу тепер можна векторизувати наступним чином (я опускаю обчислення):xj(i)z^approx=[Uj(i)]xj(i)

PRESSPCA,approx=i=1nj=1d|xj(i)[U(i)[Uj(i)]xj(i)]j|2=i=1n(IUU+diag{UU})x(i)2,

де я написав як для компактності, а означає встановлення всіх недіагональних елементів на нуль. Зауважте, що ця формула виглядає точно як перша (наївна ПРЕСА) з невеликою корекцією! Зауважте також, що ця корекція залежить лише від діагоналі , як у коді PLS_Toolbox. Однак формула все ще відрізняється від того, що, здається, реалізовано в PLS_Toolbox, і цю різницю я не можу пояснити. U d i a g {} U UU(i)Udiag{}UU

Оновлення (лютий 2018): Вище я назвав одну процедуру "правильною", а іншу - "приблизною", але я вже не впевнений, що це має сенс. Обидві процедури мають сенс, і я вважаю, що жодна з них не є більш коректною. Мені дуже подобається, що "приблизна" процедура має більш просту формулу. Крім того, я пам'ятаю, що у мене був деякий набір даних, де "приблизна" процедура давала результати, які виглядали більш значущими. На жаль, я вже не пам'ятаю деталей.


Приклади

Ось як порівняти ці методи для двох відомих наборів даних: набір даних Iris і набір даних про вино. Зауважимо, що наївний метод виробляє монотонно спадаючу криву, тоді як інші два методи дають криву з мінімумом. Далі зауважимо, що у випадку Іриса приблизний метод пропонує 1 ПК як оптимальне число, але псевдоінверсний метод пропонує 2 ПК. (І дивлячись на будь-який розсіювач PCA для набору даних Iris, здається, що обидва перші ПК несуть деякий сигнал.) А у винному випадку псевдоінверсний метод чітко вказує на 3 ПК, тоді як приблизний метод не може визначитись між 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, який я насправді не розумію (я писав у оригінальному запитанні).
Кирило

Так, вони називають це "рядковою перехресною валідацією", і ваша реалізація здається чудовою, але зауважте, що це поганий спосіб проведення перехресної перевірки (як це зазначено і також продемонстровано емпірично в Bro та ін.), І в основному вам слід ніколи не використовуйте його! Щодо рядка коду, який ви не розумієте - чи збираєтесь ви оновити своє запитання? Не впевнений, на що йдеться.
амеба

Вся справа в тому, що, схоже, ця реалізація має можливість досягти мінімуму в резюме.
Кирило

ПРЕСС дуже близький до LOO% пояснив відмінність - я б не сказав, що це добре чи погано, але це, безумовно, щось, що потрібно знати. І як% пояснена дисперсія наблизиться до 1, коли модель PCA наблизиться до x^x
ранжу

@Kirill: Спасибі, фрагмент коду має сенс зараз (можливо, ви можете видалити вищезазначені коментарі, які вже застаріли). Я поняття не маю, що це робити, але якщо ви скажете, що це робить помилку обчисленого прогнозування мінімальною, то, ймовірно, це ефективно вводить певний штраф за більший (кількість компонентів; тобто змінна у вашому коді). Чесно кажучи, я би скептично ставився до будь-якого такого методу (якщо не існує теоретичного обґрунтування цього), особливо враховуючи, що є кращі підходи, як той, який я описав у своїй відповіді. ki
амеба

1

Щоб додати ще більш загальну точку до приємної відповіді @ amoeba:

Одна практична і важлива відмінність моделей, що контролюються, і непідконтрольних полягає в тому, що для моделей, що не контролюються, потрібно думати набагато складніше, що б ви вважали еквівалентним, а що ні.

Моделі, що контролюються, завжди мають свій кінцевий результат таким чином, що про це вам не потрібно особливо дбати: за визначенням та побудовою стверджує, що має те саме значення, що і , тому ви можете безпосередньо порівнювати його. у уy^y^y

Для того, щоб побудувати змістовні заходи щодо ефективності, вам потрібно подумати, які види свободи моделі не мають сенсу для вашої програми, а які ні. Це призвело б до PRESS на бали, можливо (зазвичай?) Після якогось обертання / гортання, подібного до Прокруста.

ПРЕСС на x Моя здогадка (у мене зараз немає часу, щоб дізнатися, що роблять їх 2 рядки коду - але, можливо, ви могли б перейти через рядки і подивитися?):

Для того, щоб отримати міру, корисну для визначення хорошої складності моделі, від міри, яка забезпечує корисність, яка зазвичай збільшуватиметься до досягнення повної моделі, потрібно штрафувати за занадто складні моделі. Що, в свою чергу, означає, що ця пеналізація є а) вирішальною, і б) коригування штрафу буде коригувати обрану складність.


Побічна примітка: Я хотів би лише додати, що буду дуже обережним з таким типом автоматизованої оптимізації складності моделі. На моєму досвіді багато з цих алгоритмів дають лише псевдооб'єктивність, і часто ціною належної роботи є лише певні типи даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.