Numpy 规范化代码异常缓慢

Posted

技术标签:

【中文标题】Numpy 规范化代码异常缓慢【英文标题】:Numpy normalization code is strangely slow 【发布时间】:2013-12-16 23:32:43 【问题描述】:

我正在整理一些基本的 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])

另外,在你问之前,我对输入格式没有大量的控制权,但如果这真的是这里的限制因素,我可能会弄清楚一些事情。

【问题讨论】:

在执行操作之前,您能否在内部将master_dict 中的“矩阵”转换为 numpy 数组?我觉得你在浪费一些时间不断转换为np.array。我知道这可能不是瓶颈,但它确实是。 另外,请查看:docs.scipy.org/doc/numpy/reference/generated/…。使用这个 numpy 内置标准化函数可能会比您的 lambda 更快。 最后,如果您使用np.exp 而不是np.vectorize(lambda x: math.exp),您可能会从normalization 获得更好的性能。所有math 函数都具有numpy 等效项,并且在numpy 数组上应该比矢量化等效项更快。 @SethMMorton 感谢您的建议!可悲的是,您链接到的 scipy 文档指的是一种与我在这里所做的非常不同的规范化。最初将数组转换为 np 数组的问题是我在前几行中使用了很多 python-list 特定技巧,但是如果在 numpy 中有一个好方法可以做到这一点,我很乐意切换到更多高性能版本。最后,numpy.exp 替换实际上产生了巨大的变化!这节省了我 30% 的执行时间! 我很确定 reduce 行可以替换为 full_tensor = np.concatenate(master_dict.values()) 但在我测试的小示例中似乎并没有快多少。 【参考方案1】:

除了似乎可行的math.exp -> np.exp 建议之外,我还建议进行一些其他修改。首先,你要计算np.array(reduce(operator.add, master_dict.values())) 两次,所以在下面的返工中,我建议重用数据而不是两次工作。其次,我将您的 normalize lambda 修改为适当的函数,以便您可以预先计算数组的最小值。这样可以节省计算两次。

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,没有什么能阻止您在 numpy 数组上调用(例如)reduce。 Numpy 数组是可迭代的,因此在任何使用列表的地方都可以使用 numpy 数组(好吧,不是任何地方,但在大多数情况下)。但是,我还没有完全熟悉你的算法,也许这种情况是例外之一。

【讨论】:

【参考方案2】:

从@sethMMorton 的更改开始,我的速度几乎提高了两倍。主要来自矢量化您的normalize 函数(在_center 内部),以便您可以在整个 list_of_arrays 上调用_center,而不仅仅是将其放入列表理解中。这也消除了从 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

注意,我不会在 _center 调用中定义 normalize ,而是将其分开,如本答案所示。那么,在_center 中,只需在整个list_of_arrays 上调用normalize

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))

事实上,您可以在一开始就对整个full_tensor 调用normalize_center,而不必循环遍历,但棘手的部分是将其拆分回数组列表的字典中再次。接下来我会继续努力的: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])

【讨论】:

谢谢,大部分的加速都来自你的提示,@Seth :)

以上是关于Numpy 规范化代码异常缓慢的主要内容,如果未能解决你的问题,请参考以下文章

规范化/翻译 ndarray - Numpy / Python

如何规范化 4D numpy 数组?

如何忽略 numpy 数组中的 NaN 数据点并在 Python 中生成规范化数据?

numpy中一个点的规范表示是啥?

IDEA 代码规范插件

错误代码OpenAI权衡规范化get_variable tf1.4