Уменьшите использование памяти строки кода, которая использует NumPy - PullRequest
0 голосов
/ 11 сентября 2018

Я использую библиотеку python:

https://github.com/ficusss/PyGMNormalize

Для нормализации моих наборов данных (scRNAseq) и последней строки в файле библиотеки utils.py:

https://github.com/ficusss/PyGMNormalize/blob/master/pygmnormalize/utils.py

использует слишком много памяти:

np.percentile(matrix[np.any(matrix > 0, axis=1)], p, axis=0)

Есть ли хороший способ переписать эту строку кода, чтобы улучшить использование памяти? Я имею в виду, что 200Gb RAM доступен в кластере и с matrix чего-то вроде 20Gb эта строка не работает, но я верю, что должен быть способ заставить ее работать.

Ответы [ 2 ]

0 голосов
/ 11 сентября 2018

Если все элементы matrix>> 0, вы можете сделать:

np.percentile(matrix[np.any(matrix, axis = 1)], p, axis = 0)

При этом используется тот факт, что любое число с плавающей запятой или целое число, кроме 0, интерпретируется как True, когдарассматривается как логическое значение (что np.any делает внутренне).Избавляет вас от создания этой большой логической матрицы по отдельности.

Поскольку вы логически индексируете в matrix[...], вы создаете временную копию, которая вас не волнует, если она будет перезаписана во время percentileпроцесс.Таким образом, вы можете использовать overwrite_input = True, чтобы сэкономить еще больше памяти.

mat = matrix.copy()
perc = np.percentile(matrix[np.any(matrix, axis = 1)], p, axis = 0, overwrite_input = True)
np.array_equals(mat, matrix) # is `matrix` still the same?

True

Наконец, в зависимости от вашей другой архитектуры, я бы порекомендовал рассмотреть возможность создания matrix некоторого аромата scipy.sparse, который должен значительноснова уменьшите использование памяти (хотя с некоторыми недостатками, зависящими от типа, который вы используете).

0 голосов
/ 11 сентября 2018

Я ставлю это как ответ, так как в комментарии больше, чем уместится, хотя это может быть и не полностью. Есть две подозрительные вещи: во-первых, процентиль должен нормально работать на матрице 20 ГБ, если на вашей машине доступно 200 ГБ оперативной памяти. Это много памяти, поэтому начните изучать, что еще может быть использовано. Начните с top - есть ли другой процесс или ваша программа Python использует все это?

Вторым подозрительным моментом является то, что документация для utils.percentile не соответствует фактическому поведению. Вот соответствующие биты из кода, с которым вы связались:

def percentile(matrix, p):
    """
    Estimation of percentile without zeros.
    ....
    Returns
    -------
    float
        Calculated percentile.
    """
    return np.percentile(matrix[np.any(matrix > 0, axis=1)], p, axis=0)

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

np.any(matrix > 0, axis=1) возвращает логический массив для индексных строк, которые не являются всеми нулями. Например

>>> np.any(array([[3, 4], [0, 0]]) > 0, axis=1)
    array([ True, False])

>>> np.any(array([[3, 4], [1, 0]]) > 0, axis=1)
    array([ True,  True])

>>> np.any(array([[3, 0], [1, 0]]) > 0, axis=1)
    array([ True,  True])

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

Вычисление, которое занимает много памяти - matrix > 0 создает логический массив того же размера, что и матрица, затем индексирование создает копию matrix, которая, вероятно, содержит большинство строк.
Так, вероятно, 2-4 ГБ для логического массива и около 20 ГБ для копии.

Это можно уменьшить,

## Find rows with all zeros, one row at a time to reduce memory
mask = [np.any(r > 0) for r in matrix]  
 ## Find percentile for each column, excluding rows with all zeros
perc = [np.percentile(c[mask], p) for c in matrix.T] 

Однако, как указывалось ранее, это не соответствует документации по функции.

Может быть причина для этой логики, но она странная. Если вы не знаете причину этого, вы можете позвонить np.percentile напрямую - просто убедитесь, что он возвращает близкое значение для меньшего подмножества ваших данных. Также есть nanpercentile, который можно использовать таким же образом, но игнорирует значения nan.
Вы можете использовать логическое индексирование, чтобы заменить значения, которые вы не хотите включать, на nan (т.е. matrix[matrix < 0] = np.nan), а затем вызвать его.

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