ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 1.6。创建 Numpy 通用函数 > 原文: [http://numba.pydata.org/numba-doc/latest/user/vectorize.html](http://numba.pydata.org/numba-doc/latest/user/vectorize.html) ## 1.6.1。 `@vectorize`装饰器 Numba 的 vectorize 允许 Python 函数将标量输入参数用作 NumPy [ufuncs](http://docs.scipy.org/doc/numpy/reference/ufuncs.html) 。创建传统的 NumPy ufunc 并不是最直接的过程,而是涉及编写一些 C 代码。 Numba 让这很容易。使用 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 装饰器,Numba 可以将纯 Python 函数编译为一个 ufunc,它在 NumPy 阵列上运行的速度与用 C 编写的传统 ufunc 一样快。 使用 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") ,可以将函数编写为通过输入标量而不是数组进行操作。 Numba 将生成周围循环(或 _ 内核 _),允许对实际输入进行有效迭代。 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 装饰器有两种操作模式: * 渴望或装饰时间,编译:如果您将一个或多个类型签名传递给装饰器,您将构建 Numpy 通用函数(ufunc)。本小节的其余部分描述了使用装饰时编译构建 ufunc。 * 懒惰或调用时编译:当没有给出任何签名时,装饰器会给你一个 Numba 动态通用函数( [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") ),它在使用以前不支持的输入类型调用时动态编译新内核。后面的小节“[动态通用函数](#dynamic-universal-functions)”更深入地描述了这种模式。 如上所述,如果您将签名列表传递给 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 装饰器,您的函数将被编译为 Numpy ufunc。在基本情况下,只会传递一个签名: ```py from numba import vectorize, float64 @vectorize([float64(float64, float64)]) def f(x, y): return x + y ``` 如果您传递了多个签名,请注意必须在最不具体的签名之前传递大多数特定签名(例如,在双精度浮点数之前单精度浮点数),否则基于类型的分派将无法按预期工作: ```py @vectorize([int32(int32, int32), int64(int64, int64), float32(float32, float32), float64(float64, float64)]) def f(x, y): return x + y ``` 该函数将按预期在指定的数组类型上工作: ```py >>> a = np.arange(6) >>> f(a, a) array([ 0, 2, 4, 6, 8, 10]) >>> a = np.linspace(0, 1, 6) >>> f(a, a) array([ 0\. , 0.4, 0.8, 1.2, 1.6, 2\. ]) ``` 但它将无法在其他类型上工作: ```py >>> a = np.linspace(0, 1+1j, 6) >>> f(a, a) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ufunc 'ufunc' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe'' ``` 你可能会问自己,“为什么我要经历这个而不是使用 [@jit](jit.html#jit) 装饰器编译一个简单的迭代循环?”。答案是 NumPy ufuncs 会自动获得其他功能,如缩小,累积或广播。使用上面的例子: ```py >>> a = np.arange(12).reshape(3, 4) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> f.reduce(a, axis=0) array([12, 15, 18, 21]) >>> f.reduce(a, axis=1) array([ 6, 22, 38]) >>> f.accumulate(a) array([[ 0, 1, 2, 3], [ 4, 6, 8, 10], [12, 15, 18, 21]]) >>> f.accumulate(a, axis=1) array([[ 0, 1, 3, 6], [ 4, 9, 15, 22], [ 8, 17, 27, 38]]) ``` 也可以看看 [ufuncs 的标准功能](http://docs.scipy.org/doc/numpy/reference/ufuncs.html#ufunc)(NumPy 文档)。 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 装饰器支持多个 ufunc 目标: | 目标 | 描述 | | --- | --- | | 中央处理器 | 单线程 CPU | | 平行 | 多核 CPU | | CUDA | CUDA GPU 注意这会创建一个类似 _ufunc 的 _ 对象。有关详细信息,请参阅 CUDA ufunc 的[文档。](../cuda/ufunc.html) | 一般准则是为不同的数据大小和算法选择不同的目标。 “cpu”目标适用于小数据大小(约小于 1KB)和低计算强度算法。它具有最少的开销。 “并行”目标适用于中等数据大小(大约小于 1MB)。线程增加了一点延迟。 “cuda”目标适用于大数据量(大约 1MB)和高计算强度算法。向 GPU 传输内存和从 GPU 传输内存会增加大量开销。 ## 1.6.2。 `@guvectorize`装饰器 虽然 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 允许你一次写一个元素的 ufunc,但 [`guvectorize()`](../reference/jit-compilation.html#numba.guvectorize "numba.guvectorize") 装饰器更进一步,并允许你编写可以工作的 ufuncs 在输入数组的任意数量的元素上,并获取和返回不同维度的数组。典型的例子是运行中值或卷积滤波器。 与 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 函数相反, [`guvectorize()`](../reference/jit-compilation.html#numba.guvectorize "numba.guvectorize") 函数不返回其结果值:它们将其作为数组参数,必须由函数填充。这是因为数组实际上是由 NumPy 的调度机制分配的,调用机制调用 Numba 生成的代码。 这是一个非常简单的例子: ```py @guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)') def g(x, y, res): for i in range(x.shape[0]): res[i] = x[i] + y ``` 底层的 Python 函数只是将一个给定的标量(`y`)添加到一维数组的所有元素中。宣言更有意思。那里有两件事: * 输入和输出 _ 布局 _ 的声明,符号形式:`(n),()-&gt;(n)`告诉 NumPy 该函数采用 _n_ 元素一维数组,一个标量(用符号表示为空元组`()`)并返回 _n_ 元素一维数组; * `@vectorize`中支持的具体 _ 签名 _ 列表;这里我们只支持`int64`数组。 注意 1D 数组类型也可以接收标量参数(形状为`()`的参数)。在上面的例子中,第二个参数也可以声明为`int64[:]`。在这种情况下,该值必须由`y[0]`读取。 我们现在可以通过一个简单的例子来检查已编译的 ufunc 的作用: ```py >>> a = np.arange(5) >>> a array([0, 1, 2, 3, 4]) >>> g(a, 2) array([2, 3, 4, 5, 6]) ``` 好处是 NumPy 将根据其形状自动调度更复杂的输入: ```py >>> a = np.arange(6).reshape(2, 3) >>> a array([[0, 1, 2], [3, 4, 5]]) >>> g(a, 10) array([[10, 11, 12], [13, 14, 15]]) >>> g(a, np.array([10, 20])) array([[10, 11, 12], [23, 24, 25]]) ``` 注意 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 和 [`guvectorize()`](../reference/jit-compilation.html#numba.guvectorize "numba.guvectorize") 都支持传递`nopython=True` [,如同@jit 装饰器](jit.html#jit-nopython)。使用它来确保生成的代码不会回退到[对象模式](../glossary.html#term-object-mode)。 ## 1.6.3。动态通用功能 如上所述,如果您没有将任何签名传递给 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 装饰器,您的 Python 函数将用于构建动态通用函数,或 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 。例如: ```py from numba import vectorize @vectorize def f(x, y): return x * y ``` 结果`f()`是 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 实例,以没有支持的输入类型开头。在调用`f()`时,只要传递以前不支持的输入类型,Numba 就会生成新的内核。鉴于上面的示例,以下一组解释器交互说明了动态编译的工作原理: ```py >>> f <numba._DUFunc 'f'> >>> f.ufunc <ufunc 'f'> >>> f.ufunc.types [] ``` 上面的例子显示 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 实例不是 ufunc。而不是子类 ufunc, [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 实例通过保持 [`ufunc`](../reference/jit-compilation.html#numba.DUFunc.ufunc "numba.DUFunc.ufunc") 成员,然后将 ufunc 属性读取和方法调用委托给此成员(也称为类型聚合)来工作。当我们查看 ufunc 支持的初始类型时,我们可以验证没有。 我们试着打电话给`f()`: ```py >>> f(3,4) 12 >>> f.types # shorthand for f.ufunc.types ['ll->l'] ``` 如果这是一个普通的 Numpy ufunc,我们会看到一个异常抱怨 ufunc 无法处理输入类型。当我们用整数参数调用`f()`时,我们不仅会收到答案,而且我们可以验证 Numba 是否创建了支持 C `long`整数的循环。 我们可以通过使用不同的输入调用`f()`来添加其他循环: ```py >>> f(1.,2.) 2.0 >>> f.types ['ll->l', 'dd->d'] ``` 我们现在可以验证 Numba 是否为处理浮点输入添加了第二个循环`"dd-&gt;d"`。 如果我们将输入类型混合到`f()`,我们可以验证 [Numpy ufunc 强制转换规则](http://docs.scipy.org/doc/numpy/reference/ufuncs.html#casting-rules)是否仍然有效: ```py >>> f(1,2.) 2.0 >>> f.types ['ll->l', 'dd->d'] ``` 此示例演示了使用混合类型调用`f()`会导致 Numpy 选择浮点循环,并将整数参数转换为浮点值。因此,Numba 没有创建一个特殊的`"dl-&gt;d"`内核。 这 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 行为导致我们得到类似于上面“ [@vectorize 装饰器](#the-vectorize-decorator)”小节中给出的警告的点,但是在装饰器中没有签名声明顺序,呼叫顺序很重要。如果我们首先传入浮点参数,那么任何带有整数参数的调用都将被转换为双精度浮点值。例如: ```py >>> @vectorize ... def g(a, b): return a / b ... >>> g(2.,3.) 0.66666666666666663 >>> g(2,3) 0.66666666666666663 >>> g.types ['dd->d'] ``` 如果您需要对各种类型签名的精确支持,您应该在 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 装饰器中指定它们,而不是依赖于动态编译。