一般来说,不仅需要遍历标量上的函数,还需要遍历向量(或数组)上的函数。这个概念是通过推广通用函数(UFuncs)而在numpy中实现的。在常规的UFUNC中,基本函数仅限于逐元素操作,而通用版本(gufuncs)支持“子数组”和“子数组”操作。Perl向量库pdl提供了类似的功能,其术语在下面重复使用。
每个广义UFunc都有与其相关的信息,说明输入的“核心”维数是什么,以及输出的相应维数(元素方面的UFunc的核心维数为零)。所有参数的核心维度列表称为ufunc的“签名”。例如,ufunc numpy.add具有签名 (),()->() 定义两个标量输入和一个标量输出。
(),()->()
另一个例子是函数 inner1d(a, b) 签名为 (i),(i)->() . 这将沿着每个输入的最后一个轴应用内部积,但保持其余索引不变。例如,在哪里 a 有形状 (3, 5, N) 和 b 有形状 (5, N) ,这将返回形状的输出 (3,5) . 底层的基本函数被调用 3 * 5 时代。在签名中,我们指定了一个核心维度 (i) 对于每个输入和零芯尺寸 () 对于输出,因为它接受两个一维数组并返回一个标量。用同样的名字 i ,我们指定两个对应的尺寸应该是相同的尺寸。
inner1d(a, b)
(i),(i)->()
a
(3, 5, N)
b
(5, N)
(3,5)
3 * 5
(i)
()
i
超出核心尺寸的尺寸称为“环”尺寸。在上面的示例中,这对应于 (3, 5) .
(3, 5)
签名决定如何将每个输入/输出数组的维度拆分为核心维度和循环维度:
签名中的每个维度都与相应传入数组的维度匹配,从形状元组的结尾开始。这些是核心维度,它们必须存在于数组中,否则将引发错误。
分配给签名中相同标签的核心尺寸(例如 i 在里面 inner1d 的 (i),(i)->() )必须具有完全匹配的大小,不执行广播。
inner1d
核心维度从所有输入中移除,其余维度一起广播,定义循环维度。
每个输出的形状由循环尺寸加上输出的核心尺寸决定。
通常,输出中所有核心维度的大小将由输入数组中具有相同标签的核心维度的大小决定。这不是一个要求,并且可以定义一个在输出中第一次出现标签的签名,尽管在调用这样的函数时必须采取一些预防措施。函数就是一个例子 euclidean_pdist(a) ,带签名 (n,d)->(p) ,它给出了一个数组 n d -维向量,计算它们之间所有唯一的成对欧几里德距离。输出尺寸 p 因此必须等于 n * (n - 1) / 2 但调用者有责任传入正确大小的输出数组。如果无法从传入的输入或输出数组确定输出的核心维度的大小,则会引发错误。
euclidean_pdist(a)
(n,d)->(p)
n
d
p
n * (n - 1) / 2
注:在numpy 1.10.0之前,已经进行了不太严格的检查:缺失的核心尺寸是通过根据需要在形状上预先设置1来创建的,具有相同标签的核心尺寸是一起广播的,未确定的尺寸是使用大小1创建的。
每个ufunc都由一个基本函数组成,该函数对数组参数的最小部分执行最基本的操作(例如,添加两个数字是添加两个数组的最基本操作)。ufunc在数组的不同部分多次应用基本函数。基本函数的输入/输出可以是向量,例如inner1d的基本函数以两个向量作为输入。
签名是描述ufunc基本函数的输入/输出维度的字符串。有关更多详细信息,请参阅下面的部分。
基本函数的每个输入/输出的维数由其核心维数定义(零核心维数对应于一个标量输入/输出)。核心维度映射到输入/输出数组的最后一个维度。
维度名称表示签名中的核心维度。不同的维度可以共享一个名称,表明它们的大小相同。
维度索引是表示维度名称的整数。它根据签名中每个名称第一次出现的顺序枚举维度名称。
签名定义了输入和输出变量的“核心”维数,因此也定义了维数的收缩。签名由以下格式的字符串表示:
每个输入或输出数组的核心维度由括号中的维度名称列表表示, (i_1,...,i_N) ;标量输入/输出表示为 () . 而不是 i_1 , i_2 等等,可以使用任何有效的python变量名。
(i_1,...,i_N)
i_1
i_2
不同参数的维度列表由分隔 "," . 输入/输出参数由分隔 "->" .
","
"->"
如果在多个位置使用相同的维度名称,则会强制相应维度的大小相同。
签名的形式语法如下:
<Signature> ::= <Input arguments> "->" <Output arguments> <Input arguments> ::= <Argument list> <Output arguments> ::= <Argument list> <Argument list> ::= nil | <Argument> | <Argument> "," <Argument list> <Argument> ::= "(" <Core dimension list> ")" <Core dimension list> ::= nil | <Core dimension> | <Core dimension> "," <Core dimension list> <Core dimension> ::= <Dimension name> <Dimension modifier> <Dimension name> ::= valid Python variable name | valid integer <Dimension modifier> ::= nil | "?"
笔记:
所有报价都是为了清楚起见。
共享相同名称的未修改核心维度必须具有相同的大小。每个维度名称通常对应于基本函数实现中的一个循环级别。
忽略空白。
整数作为维度名称将该维度冻结为值。
如果名称的后缀是“?”修饰符,只有当维度存在于共享它的所有输入和输出上时,它才是核心维度;否则,它将被忽略(并替换为基本函数的大小为1的维度)。
以下是一些签名示例:
名称
签名
共同习惯
添加
二进制UFUNC
SUM1D
(i)->()
减少
内内1D
矢量乘法
马塔姆
(m,n),(n,p)->(m,p)
矩阵乘法
维卡姆
(n),(n,p)->(p)
矢量矩阵乘法
马特维克
(m,n),(n)->(m)
矩阵向量乘法
马特穆尔
(m?,n),(n,p?)->(m?,p?)
以上四项的组合
outer_inner
(i,t),(j,t)->(i,j)
内部覆盖最后一个维度,外部覆盖第二个维度到最后一个维度,循环/广播其余维度。
交叉1D
(3),(3)->(3)
最后一个维度被冻结且必须为3的叉积
最后一个是冻结核心维度的实例,可用于提高UFUNC性能。
当前接口保持不变,并且 PyUFunc_FromFuncAndData 仍然可以用来实现(专门的)UFunc,由标量初等函数组成。
PyUFunc_FromFuncAndData
一个人可以用 PyUFunc_FromFuncAndDataAndSignature 声明一个更通用的ufunc。参数列表与 PyUFunc_FromFuncAndData ,并附加一个参数,将签名指定为C字符串。
PyUFunc_FromFuncAndDataAndSignature
此外,回调函数的类型与以前相同, void (*foo)(char **args, intp *dimensions, intp *steps, void *func) . 当被调用时, args 是长度列表 nargs 包含所有输入/输出参数的数据。对于标量初等函数, steps 也是长度 nargs 表示用于论据的步幅。 dimensions 是指向单个整数的指针,定义要循环的轴的大小。
void (*foo)(char **args, intp *dimensions, intp *steps, void *func)
args
nargs
steps
dimensions
对于一个重要的签名, dimensions 也将包含核心尺寸的尺寸,从第二个入口开始。每个唯一的维度名称只提供一个大小,并且根据签名中第一次出现的维度名称给出大小。
第一 nargs 要素 steps 保持与标量UFunc相同。以下元素按顺序包含所有参数的所有核心维度的步幅。
例如,考虑一个带有签名的ufunc (i,j),(i)->() . 在这种情况下, args 将包含指向输入/输出数组数据的三个指针 a , b , c . 此外, dimensions 将 [N, I, J] 定义 N 循环和大小 I 和 J 对于核心尺寸 i 和 j . 最后, steps 将 [a_N, b_N, c_N, a_i, a_j, b_i] 包含所有必要的步伐。
(i,j),(i)->()
c
[N, I, J]
N
I
J
j
[a_N, b_N, c_N, a_i, a_j, b_i]