Numpy / Python ужасно выступают против Matlab - PullRequest
8 голосов
/ 28 сентября 2010

Начинающий программист здесь.Я пишу программу, которая анализирует относительное пространственное расположение точек (клеток).Программа получает границы и тип ячейки из массива с координатой x в столбце 1, координатой y в столбце 2 и типом ячейки в столбце 3. Затем в каждой ячейке проверяется тип ячейки и соответствующее расстояние от границ.Если он проходит, он затем вычисляет свое расстояние от каждой другой ячейки в массиве и, если расстояние находится в пределах указанного диапазона анализа, он добавляет его в выходной массив на этом расстоянии.

Моя программа маркировки ячеек находится в wxpythonтак что я надеялся также разработать эту программу на python и в итоге вставить ее в графический интерфейс.К сожалению, сейчас Python запускает основной цикл на моей машине ~ 20 секунд, в то время как MATLAB может делать ~ 15 циклов в секунду.Так как я планирую сделать 1000 циклов (с условием рандомизированного сравнения) в ~ 30 случаях, умноженных на несколько типов исследовательского анализа, это не тривиальная разница.

Я попытался запустить профилировщик, и вызовы массива выполняются в 1/4 времени, почти все остальное - неопределенное время цикла.

Вот код Python для основного цикла:

for basecell in range (0, cellnumber-1):
    if firstcelltype == np.array((cellrecord[basecell,2])):
        xloc=np.array((cellrecord[basecell,0]))
        yloc=np.array((cellrecord[basecell,1]))
        xedgedist=(xbound-xloc)
        yedgedist=(ybound-yloc)
        if xloc>excludedist and xedgedist>excludedist and yloc>excludedist and    yedgedist>excludedist:
            for comparecell in range (0, cellnumber-1):
                if secondcelltype==np.array((cellrecord[comparecell,2])):
                    xcomploc=np.array((cellrecord[comparecell,0]))
                    ycomploc=np.array((cellrecord[comparecell,1]))
                    dist=math.sqrt((xcomploc-xloc)**2+(ycomploc-yloc)**2)
                    dist=round(dist)
                    if dist>=1 and dist<=analysisdist:
                         arraytarget=round(dist*analysisdist/intervalnumber)
                         addone=np.array((spatialraw[arraytarget-1]))
                         addone=addone+1
                         targetcell=arraytarget-1
                         np.put(spatialraw,[targetcell,targetcell],addone)

Вот код Matlab для основного цикла:

for basecell = 1:cellnumber;
    if firstcelltype==cellrecord(basecell,3);
         xloc=cellrecord(basecell,1);
         yloc=cellrecord(basecell,2);
         xedgedist=(xbound-xloc);
         yedgedist=(ybound-yloc);
         if (xloc>excludedist) && (yloc>excludedist) && (xedgedist>excludedist) && (yedgedist>excludedist);
             for comparecell = 1:cellnumber;
                 if secondcelltype==cellrecord(comparecell,3);
                     xcomploc=cellrecord(comparecell,1);
                     ycomploc=cellrecord(comparecell,2);
                     dist=sqrt((xcomploc-xloc)^2+(ycomploc-yloc)^2);
                     if (dist>=1) && (dist<=100.4999);
                         arraytarget=round(dist*analysisdist/intervalnumber);
                         spatialsum(1,arraytarget)=spatialsum(1,arraytarget)+1;
                    end
                end
            end            
        end
    end
end

Спасибо!

Ответы [ 4 ]

27 голосов
/ 28 сентября 2010

Вот несколько способов ускорить ваш код Python.

Первое: Не создавайте массивы np, если вы храните только одно значение.Вы делаете это много раз в вашем коде.Например,

if firstcelltype == np.array((cellrecord[basecell,2])):

может быть просто

 if firstcelltype == cellrecord[basecell,2]:

Я покажу вам почему с некоторыми утверждениями timeit:

>>> timeit.Timer('x = 111.1').timeit()
0.045882196294822819
>>> t=timeit.Timer('x = np.array(111.1)','import numpy as np').timeit()
0.55774970267830071

Это на порядок величины вразница между этими вызовами.

Второй: Следующий код:

arraytarget=round(dist*analysisdist/intervalnumber)
addone=np.array((spatialraw[arraytarget-1]))
addone=addone+1
targetcell=arraytarget-1
np.put(spatialraw,[targetcell,targetcell],addone)

можно заменить на

arraytarget=round(dist*analysisdist/intervalnumber)-1
spatialraw[arraytarget] += 1

Третий: Вы можете избавиться от площади, о которой говорил Филипп, предварительно поставив квадрат analysisdist.Однако, поскольку вы используете analysisdist, чтобы получить arraytarget, вы можете создать отдельную переменную analysisdist2, которая является квадратом анализа и использовать ее для сравнения.

Четвертое: Вы ищете ячейки, которые соответствуют secondcelltype каждый раз, когда вы добираетесь до этой точки, вместо того, чтобы находить их один раз и использовать список снова и снова.Вы можете определить массив:

comparecells = np.where(cellrecord[:,2]==secondcelltype)[0]

и затем заменить

for comparecell in range (0, cellnumber-1):
    if secondcelltype==np.array((cellrecord[comparecell,2])):

на

for comparecell in comparecells:

Пятый: Использовать psyco.Это JIT-компилятор.Matlab имеет встроенный JIT-компилятор, если вы используете несколько более позднюю версию.Это должно немного ускорить ваш код.

Шестое: Если код все еще недостаточно быстр после всех предыдущих шагов, то вам следует попробовать векторизовать ваш код.Это не должно быть слишком сложно.В основном, чем больше вещей вы можете иметь в массивах, тем лучше.Вот моя попытка векторизации:

basecells = np.where(cellrecord[:,2]==firstcelltype)[0]
xlocs = cellrecord[basecells, 0]
ylocs = cellrecord[basecells, 1]
xedgedists = xbound - xloc
yedgedists = ybound - yloc
whichcells = np.where((xlocs>excludedist) & (xedgedists>excludedist) & (ylocs>excludedist) & (yedgedists>excludedist))[0]
selectedcells = basecells[whichcells]
comparecells = np.where(cellrecord[:,2]==secondcelltype)[0]
xcomplocs = cellrecords[comparecells,0]
ycomplocs = cellrecords[comparecells,1]
analysisdist2 = analysisdist**2
for basecell in selectedcells:
    dists = np.round((xcomplocs-xlocs[basecell])**2 + (ycomplocs-ylocs[basecell])**2)
    whichcells = np.where((dists >= 1) & (dists <= analysisdist2))[0]
    arraytargets = np.round(dists[whichcells]*analysisdist/intervalnumber) - 1
    for target in arraytargets:
        spatialraw[target] += 1

Вы, вероятно, можете удалить этот внутренний цикл for, но вы должны быть осторожны, потому что некоторые элементы массива могут быть одинаковыми.Кроме того, я на самом деле не опробовал весь код, так что там может быть ошибка или опечатка.Надеюсь, это даст вам хорошее представление о том, как это сделать.О, еще одна вещь.Вы делаете analysisdist/intervalnumber отдельной переменной, чтобы избежать повторного деления.

2 голосов
/ 28 сентября 2010

Не слишком уверен в медлительности Python, но ваш код Matlab может быть ВЫСОКО оптимизирован.Вложенные циклы, как правило, имеют ужасные проблемы с производительностью.Вы можете заменить внутренний цикл на векторизованную функцию ... как показано ниже:

for basecell = 1:cellnumber;
    if firstcelltype==cellrecord(basecell,3);
         xloc=cellrecord(basecell,1);
         yloc=cellrecord(basecell,2);
         xedgedist=(xbound-xloc);
         yedgedist=(ybound-yloc);
         if (xloc>excludedist) && (yloc>excludedist) && (xedgedist>excludedist) && (yedgedist>excludedist);
%             for comparecell = 1:cellnumber;
%                 if secondcelltype==cellrecord(comparecell,3);
%                     xcomploc=cellrecord(comparecell,1);
%                     ycomploc=cellrecord(comparecell,2);
%                     dist=sqrt((xcomploc-xloc)^2+(ycomploc-yloc)^2);
%                     if (dist>=1) && (dist<=100.4999);
%                         arraytarget=round(dist*analysisdist/intervalnumber);
%                         spatialsum(1,arraytarget)=spatialsum(1,arraytarget)+1;
%                    end
%                end
%            end
         %replace with:
        secondcelltype_mask = secondcelltype == cellrecord(:,3);
        xcomploc_vec = cellrecord(secondcelltype_mask ,1);
                ycomploc_vec = cellrecord(secondcelltype_mask ,2);
                dist_vec = sqrt((xcomploc_vec-xloc)^2+(ycomploc_vec-yloc)^2);
                dist_mask = dist>=1 & dist<=100.4999
                arraytarget_vec = round(dist_vec(dist_mask)*analysisdist/intervalnumber);
                count = accumarray(arraytarget_vec,1, [size(spatialsum,1),1]);
                spatialsum(:,1) = spatialsum(:,1)+count;
        end
    end
end

Там могут быть небольшие ошибки, поскольку у меня нет данных для тестирования кода, но он должен получитьВ 10 раз ускоряется работа с кодом Matlab.

Из моего опыта работы с numpy я заметил, что замена циклов for для векторизованной / матричной арифметики также имеет заметные ускорения.Однако без форм формы всех ваших переменных трудно векторизовать.

0 голосов
/ 28 сентября 2010

Если у вас многоядерный процессор, вы можете попробовать многопроцессорный модуль и использовать несколько процессов для использования всех ядер.

Вместо sqrt вы можете использовать х ** 0,5, что, если я правильно помню, немного быстрее.

0 голосов
/ 28 сентября 2010

Вы можете избежать некоторых вызовов math.sqrt, заменив строки

                dist=math.sqrt((xcomploc-xloc)**2+(ycomploc-yloc)**2)
                dist=round(dist)
                if dist>=1 and dist<=analysisdist:
                     arraytarget=round(dist*analysisdist/intervalnumber)

на

                dist=(xcomploc-xloc)**2+(ycomploc-yloc)**2
                dist=round(dist)
                if dist>=1 and dist<=analysisdist_squared:
                     arraytarget=round(math.sqrt(dist)*analysisdist/intervalnumber)

, где у вас есть строка

 analysisdist_squared = analysis_dist * analysis_dist

вне основного цикла вашей функции.

Поскольку math.sqrt вызывается в самом внутреннем цикле, вы должны иметь from math import sqrt в верхней части модуля и просто вызывать функцию как sqrt.

Я также попытался бы заменить

                dist=(xcomploc-xloc)**2+(ycomploc-yloc)**2

на

                dist=(xcomploc-xloc)*(xcomploc-xloc)+(ycomploc-yloc)*(ycomploc-yloc)

Существует вероятность, что он будет производить более быстрый байт-код для выполнения умножения, а не возведения в степень.

Я сомневаюсь, что это поможет вам полностью повысить производительность MATLABs, но должно помочь уменьшить некоторые накладные расходы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...