Код нормализации Numpy странно медленный


Я собираю некоторый базовый код python, который берет словарь меток, сопоставленных спискам матриц (матрицы представляют категоризированные изображения), я просто пытаюсь вычесть среднее изображение из всего, а затем центрировать данные в масштабе 0 - 1. По какой-то причине этот код кажется неуклюже медленным. При итерации всего 500 изображений 48x48 требуется около 10 секунд для запуска, что на самом деле не будет масштабироваться до количества изображений, с которыми я работаю. После просмотра cProfile результаты похоже, что большая часть времени тратится на функцию _center.

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

def __init__(self, master_dict, normalization = lambda x: math.exp(x)):
    """
    master_dict should be a dictionary mapping classes to lists of matrices

    example = {
        "cats": [[[]...], [[]...]...],
        "dogs": [[[]...], [[]...]...]
    }

    have to be python lists, not numpy arrays

    normalization represents the 0-1 normalization scheme used. Defaults to simple linear
    """
    normalization = np.vectorize(normalization)
    full_tensor = np.array(reduce(operator.add, master_dict.values()))
    centering = np.sum(np.array(reduce(operator.add, master_dict.values())), axis=0)/len(full_tensor)
    self.data = {key: self._center(np.array(value), centering, normalization) for key,value in master_dict.items()}
    self.normalization = normalization

def _center(self, list_of_arrays, centering_factor, normalization_scheme):
    """
    Centering scheme for arrays
    """
    arrays = list_of_arrays - centering_factor
    normalize = lambda a: (a - np.min(a)) / (np.max(a) - np.min(a))
    return normalization_scheme([normalize(array) for array in arrays])

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

2 3

2 ответа:

, начиная с изменения @sethMMorton, я смог получить еще почти в два раза в скорости. В основном от векторизации вашей функции normalize (Внутри _center), так что вы можете вызвать _center на весь list_of_arrays вместо того, чтобы просто поместить его в список понимания. Это также избавляет от дополнительного преобразования из массива numpy в список и обратно.

def normalize(a):
    a -= a.min(1, keepdims=True).min(2, keepdims=True)
    a /= a.max(1, keepdims=True).max(2, keepdims=True)
    return a
Обратите внимание, что я не буду определять normalize внутри вызова _center, но оставлю его отдельным, как показано в этом ответе. Итак, в _center, просто вызовите normalize на весь list_of_arrays:
def _center(self, list_of_arrays, centering_factor, normalization_scheme):
    """
    Centering scheme for arrays
    """
    list_of_arrays -= centering_factor
    return normalization_scheme(normalize(list_of_arrays))

На самом деле, вы можете вызвать normalize и _center на весь full_tensor в самом начале, и никогда не придется делать цикл, но хитрая часть состоит в том, чтобы снова разбить его на список списков массивов. Я буду работать над этим дальше: P


Как уже упоминалось в моем комментарии, Вы можете заменить:

full_tensor = np.array(reduce(operator.add, master_dict.values()))

С

full_tensor = np.concatenate(master_dict.values())

Что может быть не быстрее, но это более понятный и стандартный способ сделать это.

В конец, вот тайминги:

>>> timeit slater_init(example)
1 loops, best of 3: 1.42 s per loop

>>> timeit seth_init(example)
1 loops, best of 3: 489 ms per loop

>>> timeit my_init(example)
1 loops, best of 3: 281 ms per loop

Ниже приведен мой полный код для синхронизации. Обратите внимание, что я заменил self.data = ... на return ... , чтобы я мог сохранить и сравнить выходные данные, чтобы убедиться, что весь наш код возвращает одни и те же данные :) конечно, вы также должны протестировать свою версию против моей!

import operator
import math
import numpy as np

#example dict has N keys (integers), each value is a list of n random HxW 'arrays', in list form:
test_shape = 10, 2, 4, 4          # small example for testing
timing_shape = 100, 5, 48, 48     # bigger example for timing
N, n, H, W = timing_shape
example = dict(enumerate(np.random.rand(N, n, H, W).tolist()))

def my_init(master_dict, normalization=np.exp):
    full_tensor = np.concatenate(master_dict.values())
    centering = np.mean(full_tensor, 0)
    return {key: my_center(np.array(value), centering, normalization)
                     for key,value in master_dict.iteritems()} #use iteritems here
    #self.normalization = normalization

def my_normalize(a):
    a -= a.min(1, keepdims=True).min(2, keepdims=True)
    a /= a.max(1, keepdims=True).max(2, keepdims=True)
    return a

def my_center(arrays, centering_factor, normalization_scheme):
    """
    Centering scheme for arrays
    """
    arrays -= centering_factor
    return normalization_scheme(my_normalize(arrays))

#### sethMMorton's original improvement ####

def seth_init(master_dict, normalization = np.exp):
    """
    master_dict should be a dictionary mapping classes to lists of matrices

    example = {
        "cats": [[[]...], [[]...]...],
        "dogs": [[[]...], [[]...]...]
    }

    have to be python lists, not numpy arrays

    normalization represents the 0-1 normalization scheme used. Defaults to simple linear
    """
    full_tensor = np.array(reduce(operator.add, master_dict.values()))
    centering = np.sum(full_tensor, axis=0)/len(full_tensor)
    return {key: seth_center(np.array(value), centering, normalization) for key,value in master_dict.items()}
    #self.normalization = normalization

def seth_center(list_of_arrays, centering_factor, normalization_scheme):
    """
    Centering scheme for arrays
    """
    def seth_normalize(a):
        a_min = np.min(a)
        return (a - a_min) / (np.max(a) - a_min)
    arrays = list_of_arrays - centering_factor
    return normalization_scheme([seth_normalize(array) for array in arrays])

#### Original code, by slater ####

def slater_init(master_dict, normalization = lambda x: math.exp(x)):
    """
    master_dict should be a dictionary mapping classes to lists of matrices

    example = {
        "cats": [[[]...], [[]...]...],
        "dogs": [[[]...], [[]...]...]
    }

    have to be python lists, not numpy arrays

    normalization represents the 0-1 normalization scheme used. Defaults to simple linear
    """
    normalization = np.vectorize(normalization)
    full_tensor = np.array(reduce(operator.add, master_dict.values()))
    centering = np.sum(np.array(reduce(operator.add, master_dict.values())), axis=0)/len(full_tensor)
    return {key: slater_center(np.array(value), centering, normalization) for key,value in master_dict.items()}
    #self.normalization = normalization

def slater_center(list_of_arrays, centering_factor, normalization_scheme):
    """
    Centering scheme for arrays
    """
    arrays = list_of_arrays - centering_factor
    slater_normalize = lambda a: (a - np.min(a)) / (np.max(a) - np.min(a))
    return normalization_scheme([slater_normalize(array) for array in arrays])

В дополнение к предложению math.exp -> np.exp, которое, казалось, сработало, я также предлагаю несколько других модификаций. Во-первых, вы выполняете вычисление np.array(reduce(operator.add, master_dict.values())) дважды, поэтому в следующей работе я предлагаю повторно использовать данные вместо выполнения работы дважды. Во-вторых, я изменил вашу normalize лямбду, чтобы она была правильной функцией, так что вы можете предварительно вычислить min массива. Это экономит вычисление дважды.

def __init__(self, master_dict, normalization = np.exp):
    """
    master_dict should be a dictionary mapping classes to lists of matrices

    example = {
        "cats": [[[]...], [[]...]...],
        "dogs": [[[]...], [[]...]...]
    }

    have to be python lists, not numpy arrays

    normalization represents the 0-1 normalization scheme used. Defaults to simple linear
    """
    full_tensor = np.array(reduce(operator.add, master_dict.values()))
    centering = np.sum(full_tensor, axis=0)/len(full_tensor)
    self.data = {key: self._center(np.array(value), centering, normalization) for key,value in master_dict.items()}
    self.normalization = normalization

def _center(self, list_of_arrays, centering_factor, normalization_scheme):
    """
    Centering scheme for arrays
    """
    def normalize(a):
        a_min = np.min(a)
        return (a - a_min) / (np.max(a) - a_min)
    arrays = list_of_arrays - centering_factor
    return normalization_scheme([normalize(array) for array in arrays])

Я с уважением отношусь к вашему комментарию о необходимости делать определенные вещи python, чтобы вы не могли конвертировать чтобы arrays перед манипуляцией данными, ничто не мешает вам вызвать (например) reduce на массиве numpy. Массивы Numpy являются итерационными, поэтому везде, где вы используете список, вы можете использовать массив numpy (хорошо, не везде, но в большинстве случаев). Однако я не полностью ознакомился с вашим алгоритмом, и, возможно, этот случай является одним из исключений.