Как я могу ускорить этот вызов квантиля в 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(...) также возвращает интервал матрицы индексов. Если x вектор, n(k) = >sum(bin==k). bin равен нулю для значений вне диапазона. Если x - матрица размера M на 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
Пожалуйста. Я собирался предложить использовать реплику соответствующей i-й строки матрицы q внутри цикла для выполнения теста ›=q. X›=repmat(q(i,:),size(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 и 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