Улучшение использования памяти в массивном фильтре, чтобы избежать обработки блоков - PullRequest
3 голосов
/ 10 февраля 2011

Я использую некоторые фильтры спутниковых изображений, начиная с фильтра, который называется Enhanced Lee.Изображения легко до 5000x5000 пикселей и более.Моя текущая реализация исчерпывает память, пытаясь вычислить фильтры для этих больших массивов (обратите внимание, что фильтры скользящего среднего и скользящего stddev могут быть запущены за один раз).Основная сложность заключается в количестве массивов, которые должны храниться в памяти, чтобы вернуть окончательно отфильтрованный массив.В этом вопросе я попросил помощи по уточнению функции обработки блоков, но мой вопрос: есть ли способ улучшить этот код, чтобы мне не нужно было использовать обработку блоков?

    def moving_average(Ic, filtsize):
        Im = numpy.empty(Ic.shape, dtype='Float32')
        scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
        return Im

    def moving_stddev(Ic, filtsize):
        Im = numpy.empty(Ic.shape, dtype='Float32')
        scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
        S = numpy.empty(Ic.shape, dtype='Float32')
        scipy.ndimage.filters.uniform_filter(((Ic-Im) ** 2), filtsize, output=S)
        return numpy.sqrt(S)

    def enh_lee(Ic, filtsize, nlooks, dfactor):
        # Implementation based on PCI Geomatica's FELEE function documentation
        Ci = moving_stddev(Ic, filtsize) / moving_average(Ic, filtsize) #1st array in memory
        Cu = numpy.sqrt(1 / nlooks) #scalar
        Cmax = numpy.sqrt(1 + (2 * nlooks)) #scalar
        W = numpy.exp(-dfactor * (Ci - Cu) / (Cmax - Ci)) #2nd array in memory
        Im = moving_average(Ic, filtsize) #3rd array in memory
        If = Im * W + Ic * (1 - W) #4th array in memory
        W = None # Back to 3 arrays in memory
        return numpy.select([Ci <= Cu, (Cu < Ci) * (Ci < Cmax), Ci >= Cmax], [Im, If, Ic])

, где nlooks и dfactor - скаляры, а Ic - нефильтрованный массив.

РЕДАКТИРОВАТЬ, основываясь на ваших предложениях (я также смотрю на numberxpr ), мой улучшенный код для enh_lee выглядит следующим образом, но его все же недостаточно, чтобы пройти последний шаг без выполнениянедостаточно памяти:

def enh_lee(Ic, filtsize, nlooks, dfactor):
    Im = moving_average(Ic, filtsize)
    Ci = moving_stddev(Ic, filtsize)
    Ci /= Im
    Cu = numpy.sqrt(1 / nlooks)
    Cmax = numpy.sqrt(1 + (2 * nlooks))

    W = Ci
    W -= Cu
    W /= Cmax - Ci
    W *= -dfactor
    numpy.exp(W, W)

    If = 1
    If -= W
    If *= Ic
    If += Im * W
    W = None
    return numpy.select([Ci <= Cu, (Cu < Ci) & (Ci < Cmax), Ci >= Cmax], [Im, If, Ic])

Ответы [ 3 ]

5 голосов
/ 10 февраля 2011

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

  1. Большинство функций numpy принимают параметр out, который можно использовать дляукажите выходной массив вместо возврата копии.Например, np.sqrt(x, x) возьмет квадратный корень массива на месте.
  2. x += 1 использует половину памяти, которую x = x + 1 делает, поскольку последняя создает временную копию.Когда это возможно, попробуйте разделить вычисления на *=, +=, /= и т. Д. Если это не так, используйте Numberxpr, как предложено @eumiro.(Или просто использовал NumberxPr независимо от ... Во многих случаях это довольно удобно.)

Итак, во-первых, вот производительность вашей исходной функции с массивом случайных данных 10000x10000 и filtsize из 3:

Профиль использования памяти исходной функции enter image description here

Что интересно, так это большие шипы на конце.Это происходит во время вашего numpy.select(...) бита.Есть много мест, где вы случайно создаете дополнительные временные массивы, но они в основном не имеют значения, так как они перегружены тем, что происходит во время вызова select.


в любомОцените, если мы заменим ваш оригинальный (чистый и компактный) код этой довольно многословной версией, вы можете значительно оптимизировать использование памяти:

import numpy
import scipy.ndimage

def main(x=None):
    if x is None:
        ni, nj = 10000, 10000
        x = numpy.arange(ni*nj, dtype=numpy.float32).reshape(ni,nj)
    filtsize = 3
    nlooks = 10.0
    dfactor = 10.0
    x = enh_lee(x, filtsize, nlooks, dfactor)
    return x

def moving_average(Ic, filtsize):
    Im = numpy.empty(Ic.shape, dtype='Float32')
    scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
    return Im

def moving_stddev(Ic, filtsize):
    Im = numpy.empty(Ic.shape, dtype='Float32')
    scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
    Im *= -1
    Im += Ic
    Im **= 2
    scipy.ndimage.filters.uniform_filter(Im, filtsize, output=Im)
    return numpy.sqrt(Im, Im)

def enh_lee(Ic, filtsize, nlooks, dfactor):
    # Implementation based on PCI Geomatica's FELEE function documentation
    Ci = moving_stddev(Ic, filtsize) 
    Im = moving_average(Ic, filtsize) 
    Ci /= Im

    Cu = numpy.sqrt(1 / nlooks).astype(numpy.float32) 
    Cmax = numpy.sqrt(1 + (2 * nlooks)).astype(numpy.float32) 

    W = Ci.copy()
    W -= Cu
    W *= -dfactor
    W /= Cmax - Ci
    W = numpy.exp(W, W)

    If = Im * W
    W *= -1
    W += 1
    W *= Ic
    If += W
    del W

    # Replace the call to numpy.select
    out = If
    filter = Ci <= Cu
    numpy.putmask(out, filter, Im)
    del Im

    filter = Ci >= Cmax
    numpy.putmask(out, filter, Ic)
    return out

if __name__ == '__main__':
    main()

Вот полученный профиль памяти для этого кода:

Профиль использования памяти оптимизированной версией на базе Numpy enter image description here

Итак, мы значительно сократили использование памяти, но код несколько менее читабелен (imo).

Однако эти последние три пика являются двумя numpy.where вызовами ...

Если numpy.where принял параметр out, мы могли бы еще больше уменьшить пиковое использование памяти еще на ~ 300 МБили так.К сожалению, этого не происходит, и я не знаю более эффективного способа памяти ...

Мы можем использовать numpy.putmask для замены вызова на numpy.select ивыполнить операцию на месте (спасибо @eumiro за , упомянув это в совершенно другом вопросе .)

Если мы оптимизируем вещи с помощью Numberxpr, мы получим значительно более чистый код (по сравнению с чистым- версия выше, а не оригинал).Возможно, вы могли бы немного уменьшить использование памяти в этой версии ... Я не очень знаком с Numberxpr, за исключением того, что использовал его несколько раз.

import numpy
import scipy.ndimage
import numexpr as ne

def main(x=None):
    if x is None:
        ni, nj = 10000, 10000
        x = numpy.arange(ni*nj, dtype=numpy.float32).reshape(ni,nj)
    filtsize = 3
    nlooks = 10.0
    dfactor = 10.0
    x = enh_lee(x, filtsize, nlooks, dfactor)
    return x

def moving_average(Ic, filtsize):
    Im = numpy.empty(Ic.shape, dtype='Float32')
    scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
    return Im

def moving_stddev(Ic, filtsize):
    Im = numpy.empty(Ic.shape, dtype='Float32')
    scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
    Im = ne.evaluate('((Ic-Im) ** 2)')
    scipy.ndimage.filters.uniform_filter(Im, filtsize, output=Im)
    return ne.evaluate('sqrt(Im)')

def enh_lee(Ic, filtsize, nlooks, dfactor):
    # Implementation based on PCI Geomatica's FELEE function documentation
    Ci = moving_stddev(Ic, filtsize) 
    Im = moving_average(Ic, filtsize) 
    Ci /= Im

    Cu = numpy.sqrt(1 / nlooks).astype(numpy.float32) 
    Cmax = numpy.sqrt(1 + (2 * nlooks)).astype(numpy.float32) 

    W = ne.evaluate('exp(-dfactor * (Ci - Cu) / (Cmax - Ci))') 
    If = ne.evaluate('Im * W + Ic * (1 - W)') 
    del W

    out = ne.evaluate('where(Ci <= Cu, Im, If)') 
    del Im
    del If

    out = ne.evaluate('where(Ci >= Cmax, Ic, out)')
    return out

if __name__ == '__main__':
    main()

А вот профиль использования памяти для версии Numberxpr: (Обратите внимание, что время выполнения более чем в два раза по сравнению с оригиналом!)

Профиль использования памятиоптимизированной версии на основе Numexpr * enter image description here

Наибольшее использование памяти по-прежнему сохраняется во время вызовов на where (вместо вызова на select).Тем не менее, пиковое использование памяти было значительно сокращено.Самый простой способ еще больше уменьшить это - найти способ работы на одном из массивов select.Это было бы довольно легко сделать с помощью Cython (вложенные циклы были бы довольно медленными в чистом Python, и любой тип логического индексирования в Numpy создаст дополнительную копию).Вам может быть лучше, просто разделив входной массив, как вы делали, хотя ...

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

3 голосов
/ 10 февраля 2011

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

2 голосов
/ 10 февраля 2011

В качестве примера, как уменьшить использование памяти, давайте посмотрим на вашу функцию moving_stddev(). Выражения типа

((Ic-Im) ** 2)

создаст временные массивы, но их можно избежать:

def moving_stddev(Ic, filtsize):
    Im = numpy.empty(Ic.shape, dtype='Float32')
    scipy.ndimage.filters.uniform_filter(Ic, filtsize, output=Im)
    Im -= Ic
    Im **= 2
    scipy.ndimage.filters.uniform_filter(Im, filtsize, output=Im)
    numpy.sqrt(Im, Im)
    return Im

Обратите внимание, что numpy.sqrt() и scipy.ndimage.filters.uniform_filter() отлично работают с использованием одинакового массива ввода и вывода. Будьте осторожны с этими конструкциями, потому что иногда это имеет неожиданные побочные эффекты.

Джо Кингтон дает хороший ответ *1013*, подробно описывающий больше способов экономии памяти.

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