Как предотвратить эту утечку памяти при логическом индексировании numpy - PullRequest
0 голосов
/ 21 апреля 2020

Я пытаюсь оптимизировать метод в numpy, оценивая гауссову функцию во многих позициях на изображении. Сейчас я вычисляю логические маски вокруг центральных положений каждого гауссиана и использую их для индексации массива данных, чтобы сэкономить время вычислений. Для небольшого количества точек данных логическое индексирование значительно ускорило сценарий по сравнению с вычислением индексов с помощью np.argwhere.

К сожалению, похоже, что логическое индексирование вызывает утечку памяти. Следующий пример заставляет мою RAM максимально Как мне это предотвратить? Это ошибка numpy, или я что-то не так делаю? Я думаю, что это может быть потому, что логическое индексирование создает копии данных, как кто-то написал в этот ответ . Разве память не должна быть освобождена после того, как копия больше не нужна? Есть ли способ вручную освободить память?

import numpy as np
import matplotlib.pyplot as plt


class Image():
    def __init__(self, nx, ny, px):
        x = np.linspace(0, nx * px, nx)
        y = np.linspace(0, ny * px, ny)
        self.x, self.y = np.meshgrid(x, y)
        self.data = np.zeros((nx, ny), dtype=np.uint8)
        self.nx = nx  # number of pixels
        self.ny = ny
        self.px = px  # pixel size [mm]

    def gaussian(self, x, y, x0, y0, amplitude=None, sigma=0.01):
        """ 2d gaussian function

        Parameters:
            x, y: coordinates of evaluation position
            x0, y0: center coordinates of Gaussian
            amplitude: amplitude of the Gaussian
            sigma: width of Gaussian
        """
        if amplitude is None:
            amplitude = np.iinfo(self.data.dtype).max
        result = amplitude * np.exp(
            -((x - x0)**2 + (y - y0)**2) / (2 * sigma ** 2))
        return result.astype(self.data.dtype)

    def mask_from_pos(self, x0, y0, radius_px):
        """ returns a square mask around the position (x0, y0)
        """
        masks = np.zeros((x0.size, self.ny, self.nx), dtype=bool)
        i_center_x = (x0 // self.px).astype(int)
        i_center_y = (y0 // self.px).astype(int)
        for ix, iy, mask in zip(i_center_x, i_center_y, masks):
            y_start = max(0, min(iy - radius_px, self.ny - 1))
            y_end = max(0, min(iy + radius_px + 1, self.ny))
            x_start = max(0, min(ix - radius_px, self.nx - 1))
            x_end = max(0, min((ix + radius_px + 1), self.nx))

            mask[y_start: y_end, x_start: x_end] = True
        return masks

    def add_gaussians(self, posx, posy, radius_px=9):
        masks = self.mask_from_pos(posx, posy, radius_px)
        for x0, y0, mask in zip(posx, posy, masks):
            self.data[mask] += self.gaussian(
                self.x[mask], self.y[mask], x0, y0).astype(self.data.dtype)


if __name__ == '__main__':
    image = Image(2000, 2000, 0.005)
    xy_max = 2000 * 0.005
    x0 = np.random.rand(3000) * xy_max
    y0 = np.random.rand(3000) * xy_max
    image.add_gaussians(x0, y0)

    plt.figure(dpi=300, figsize=(8, 8))
    plt.imshow(image.data,
            cmap=plt.get_cmap('gray'),
            extent=[0, xy_max, 0, xy_max])

Я уже пробовал отлаживать это, и ни один из объектов python не растет, поэтому я предполагаю, что это проблема в numpy, но я Я не уверен на 100%. Любая помощь в этом с благодарностью! Заранее спасибо.

Редактировать 1: Обходной путь

Я нашел решение, которое дает тот же результат, но без переполнения памяти. Простая замена вычисления массива-маски генератором-маской сделала свое дело:

    def mask_from_pos_gen(self, x0, y0, radius_px):
        """ yields a square mask around the position (x0, y0)
        """
        i_center_x = (x0 // self.px).astype(int)
        i_center_y = (y0 // self.px).astype(int)
        for ix, iy in zip(i_center_x, i_center_y):
            mask = np.zeros(self.data.shape, dtype=bool)
            y_start = max(0, min(iy - radius_px, self.ny - 1))
            y_end = max(0, min(iy + radius_px + 1, self.ny))
            x_start = max(0, min(ix - radius_px, self.nx - 1))
            x_end = max(0, min((ix + radius_px + 1), self.nx))

            mask[y_start: y_end, x_start: x_end] = True
            yield mask

Этот метод затем можно использовать точно так же, как и mask_from_pos ранее, но память освобождается таким образом. Оставьте это здесь на случай, если у кого-то есть такая же проблема, и надеетесь, что какой-нибудь numpy Гуру объяснит это поведение.

1 Ответ

0 голосов
/ 22 апреля 2020

Оказывается, что boolan indexing - не лучшее решение для этой проблемы. Использование среза numpy дает значительное увеличение скорости по сравнению с булевыми масками.

    def slice_from_pos_gen(self, x0, y0, radius_px):
        """ yields a square mask around the position (x0, y0)
        """
        i_center_x = (x0 // self.px).astype(int)
        i_center_y = (y0 // self.px).astype(int)
        for ix, iy in zip(i_center_x, i_center_y):
            y_start = max(0, min(iy - radius_px, self.ny - 1))
            y_end = max(0, min(iy + radius_px + 1, self.ny))
            x_start = max(0, min(ix - radius_px, self.nx - 1))
            x_end = max(0, min((ix + radius_px + 1), self.nx))

            yield np.s_[y_start: y_end, x_start: x_end]

Этот метод затем можно использовать точно так же, как и mask_from_pos ранее, но он намного быстрее и не заполняется память.

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