Код нормализации 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 ответа:
, начиная с изменения @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 (хорошо, не везде, но в большинстве случаев). Однако я не полностью ознакомился с вашим алгоритмом, и, возможно, этот случай является одним из исключений.