Как мога да ускоря това извикване на квантил в Matlab?

Имам MATLAB рутина с едно доста очевидно тясно място. Профилирах функцията, в резултат на което 2/3 от изчислителното време се използва във функцията levels:

въведете описание на изображението тук

Функцията levels взема матрица от плаващи числа и разделя всяка колона на nLevels кофи, връщайки матрица със същия размер като входа, като всеки запис се заменя с номера на кофата, в която попада.

За да направя това, използвам функцията quantile, за да получа ограниченията на кофата, и цикъл, за да присвоя записите на кофи. Ето моята реализация:

function [Y q] = levels(X,nLevels)
% "Assign each of the elements of X to an integer-valued level"

p = linspace(0, 1.0, nLevels+1);

q = quantile(X,p);
if isvector(q)
    q=transpose(q);
end

Y = zeros(size(X));

for i = 1:nLevels
    % "The variables g and l indicate the entries that are respectively greater than
    % or less than the relevant bucket limits. The line Y(g & l) = i is assigning the
    % value i to any element that falls in this bucket."
    if i ~= nLevels % "The default; doesnt include upper bound"
        g = bsxfun(@ge,X,q(i,:));
        l = bsxfun(@lt,X,q(i+1,:));
    else            % "For the final level we include the upper bound"
        g = bsxfun(@ge,X,q(i,:));
        l = bsxfun(@le,X,q(i+1,:));
    end
    Y(g & l) = i;
end

Има ли нещо, което мога да направя, за да ускоря това? Може ли кодът да бъде векторизиран?


person Chris Taylor    schedule 22.12.2011    source източник
comment
какъв профайлър използвахте, за да получите този профил? 10x   -  person 0x90    schedule 22.12.2011


Отговори (5)


Ако разбирам правилно, искате да знаете колко предмета са паднали във всяка кофа. Използвайте:

n = хист(Y,nbins)

Въпреки че не съм сигурен, че ще помогне за ускоряването. Просто така е по-чисто.

Редактиране: След коментара:

Можете да използвате втория изходен параметър на histc

[n,bin] = histc(...) също връща bin матрица на индекс. Ако x е вектор, n(k) = >sum(bin==k). bin е нула за стойности извън диапазона. Ако x е матрица M-by-N, тогава

person Andrey Rubshtein    schedule 22.12.2011
comment
Не, искам да знам в коя кофа са попаднали артикулите. Например, с входния вектор (3, 1, 4, 6, 7, 2), ако изпълня моята функция levels върху него с nLevels=2, бих очаквал да видя изхода (1, 1, 2, 2, 2, 1 ). - person Chris Taylor; 22.12.2011
comment
Това е страхотно, благодаря! Трябваше да напиша цикъл върху колоните, така че всяка колона да се третира индивидуално, но това изглежда се представя много по-добре. Ще актуализирам отговора си с подробности. Гласуваха за. - person Chris Taylor; 22.12.2011

Какво ще кажете за това

function [Y q] = levels(X,nLevels)

p = linspace(0, 1.0, nLevels+1);
q = quantile(X,p); 
Y = zeros(size(X));
for i = 1:numel(q)-1    
    Y = Y+ X>=q(i);
end

Това води до следното:

>>X = [3 1 4 6 7 2];
>>[Y, q] = levels(X,2)

Y =

     1  1  2  2  2  1

q =

     1  3.5  7

Можете също така да промените логическата линия, за да гарантирате, че стойностите са по-малки от началото на следващия кош. Не мисля обаче, че е необходимо.

person Aero Engy    schedule 22.12.2011
comment
Това поставя записите на цялата матрица в кофи, но не прави колоните поотделно. Вие обаче ми дадохте идея да ускоря кода си - ще ви уведомя, ако работи! - person Chris Taylor; 22.12.2011
comment
@ChrisTaylor Пропуснах частта във въпроса ви, където трябваше да се работи с всяка колона. Не би трябвало да е трудно да промените горното, за да функционира по този начин. Уведомете ме, ако имате нужда от още помощ. - person Aero Engy; 22.12.2011
comment
Благодаря, използвах идеята ви за добавяне на масив от логики на всеки етап от цикъла в моя собствен отговор (по-долу). Даде ми прилично ускорение, така че съм много благодарен! Гласуваха за. - person Chris Taylor; 22.12.2011
comment
Вие сте добре дошъл. Щях да предложа да използвате repmat на съответния i-ти ред на матрицата q вътре в цикъла, за да направите ›=q теста. X›=repmat(q(i,:),размер(X,1),1); Въпреки това не бях запознат с bsxfun() и изглежда, че се грижи за това сам. – - person Aero Engy; 22.12.2011

Мисля, че трябва да използвате histc

[~,Y] = histc(X,q)

Както можете да видите в документа на matlab:

Описание

n = histc(x,edges) отчита броя на стойностите във вектор x, които попадат между елементите във вектора на ръбовете (който трябва да съдържа монотонно ненамаляващи стойности). n е вектор на дължина (ръбове), съдържащ тези преброявания. Никакви елементи от x не могат да бъдат сложни.

person Oli    schedule 22.12.2011
comment
Сигурно съм бил неясен. Искам да знам в коя кофа попада всеки запис в моя вход. Например, ако дам входовете x=[3 1 4 6 7 2] и nLevels=2, бих очаквал да видя изхода [1 1 2 2 2 1], защото 1-ви, 2-ри и 6-ти записи попадат в първата кофа (т.е. най-малката половина, тъй като имаме само 2 кофи), а останалите попадат в втората кофа. - person Chris Taylor; 22.12.2011

Направих няколко подобрения (включително една, вдъхновена от Aero Engy в друг отговор), които доведоха до някои подобрения. За да ги изпробвам, създадох произволна матрица от милион реда и 100 колони, за да изпълнявам подобрените функции на:

>> x = randn(1000000,100);

Първо пуснах немодифицирания си код със следните резултати:

въведете описание на изображението тук

Обърнете внимание, че от 40-те секунди около 14 от тях са изразходвани за изчисляване на квантилите - не мога да очаквам да подобря тази част от рутината (предполагам, че Mathworks вече са я оптимизирали, въпреки че предполагам, че това предполага, че прави... )

След това модифицирах рутината до следното, което трябва да е по-бързо и има предимството, че е с по-малко редове!

function [Y q] = levels(X,nLevels)

p = linspace(0, 1.0, nLevels+1);
q = quantile(X,p);
if isvector(q), q = transpose(q); end

Y = ones(size(X));

for i = 2:nLevels
    Y = Y + bsxfun(@ge,X,q(i,:));
end

Резултатите от профилирането с този код са:

въведете описание на изображението тук

Така че е с 15 секунди по-бързо, което представлява 150% ускорение на частта от кода, която е моя, а не на MathWorks.

Накрая, следвайки предложение на Андрей (отново в друг отговор), модифицирах кода, за да използвам втория изход на функцията histc, която присвоява записи в контейнери. Той не третира колоните независимо, така че трябваше да превъртя колоните ръчно, но изглежда, че се представя много добре. Ето кода:

function [Y q] = levels(X,nLevels)

p = linspace(0,1,nLevels+1);

q = quantile(X,p);
if isvector(q), q = transpose(q); end
q(end,:) = 2 * q(end,:);

Y = zeros(size(X));

for k = 1:size(X,2)
    [junk Y(:,k)] = histc(X(:,k),q(:,k));
end

И резултатите от профилирането:

въведете описание на изображението тук

Сега прекарваме само 4,3 секунди в кодове извън функцията quantile, което е около 500% ускорение спрямо това, което написах първоначално. Прекарах малко време в писането на този отговор, защото смятам, че се превърна в хубав пример за това как можете да използвате MATLAB Profiler и StackExchange в комбинация, за да получите много по-добра производителност от вашия код.

Доволен съм от този резултат, но, разбира се, ще продължа да се радвам да чуя други отговори. На този етап основното увеличение на производителността ще дойде от увеличаване на производителността на частта от кода, която в момента извиква quantile. Не виждам как да направя това веднага, но може би някой друг тук може. Благодаря отново!

person Chris Taylor    schedule 22.12.2011

Можете да sort колоните и да разделите+закръглете обратните индекси:

function Y = levels(X,nLevels)
% "Assign each of the elements of X to an integer-valued level"
[S,IX]=sort(X);
[grid1,grid2]=ndgrid(1:size(IX,1),1:size(IX,2));
invIX=zeros(size(X));
invIX(sub2ind(size(X),IX(:),grid2(:)))=grid1;
Y=ceil(invIX/size(X,1)*nLevels);

Или можете да използвате tiedrank:

function Y = levels(X,nLevels)
% "Assign each of the elements of X to an integer-valued level"
R=tiedrank(X);
Y=ceil(R/size(X,1)*nLevels);

Изненадващо и двете решения са малко по-бавни от решението quantile+histc.

person cyborg    schedule 22.12.2011
comment
Благодаря, това ми даде малко ускорение над предложението на Андрей - сега гледам на 14 секунди, а не на 18 секунди. Ще добавя резултатите от профила в отговора си, когато имам време. - person Chris Taylor; 22.12.2011