Python Numpy安全编程
使用numpy进行安全、无bug的数值编程有什么来源或指南吗 我这么问是因为我痛苦地了解到numpy做了很多事情,似乎真的会让bug发生,比如 添加不同大小的矩阵(“广播”),无需抱怨:Python Numpy安全编程,python,numpy,Python,Numpy,使用numpy进行安全、无bug的数值编程有什么来源或指南吗 我这么问是因为我痛苦地了解到numpy做了很多事情,似乎真的会让bug发生,比如 添加不同大小的矩阵(“广播”),无需抱怨: In: np.array([1]) + np.identity(2) Out: array([[ 2., 1.], [ 1., 2.]]) 根据输入返回不同的数据类型: In: scalar1 = 1 In: scalar2 = 1. In: np.array(scalar1).d
In: np.array([1]) + np.identity(2)
Out: array([[ 2., 1.],
[ 1., 2.]])
根据输入返回不同的数据类型:
In: scalar1 = 1
In: scalar2 = 1.
In: np.array(scalar1).dtype
Out: dtype('int32')
In: np.array(scalar2).dtype
Out: dtype('float64')
或者只是不执行所需的操作(同样,取决于数据类型),而不发出任何警告:
In: np.squeeze(np.array([[1, 1]])).ndim
Out: 1
In: np.squeeze(np.matrix([[1, 1]])).ndim
Out: 2
这些bug都很难发现,因为它们不会引发任何异常或警告,并且通常会返回有效数据类型/形状的结果。因此,我的问题是:在使用numpy进行数学编程时,是否有提高安全性和防止错误的一般指导原则?
[请注意,我不认为这个答案会吸引“固执己见的答案和讨论”,因为它不是关于个人建议,而是询问是否有关于这个主题的任何现有指导方针或来源,我找不到任何指导方针或来源。]如果您想以“更安全”的方式使用numpy,你可能需要建立自己的安全网。一种方法是定义包装器,以强制执行您希望代码遵守的规则。你可以想出你自己的包装和测试,当你走和/或绊倒的行为,你认为有问题。 一些玩具示例: 始终使用浮点数组: 禁用广播: 使用
np.matrix
时发出警告:
在某些方面,这个问题的答案与安全编程的一般指南没有什么不同:
- 对于每个功能,尽早检查并清理代码
- 维护相关的单元测试
numpy特有的一件事是处理浮动错误;默认设置只是将警告语句“打印”到stdout,这很容易被忽略(并且不适合适当的异常处理工作流)。您可以将此功能转换为抛出可以通过
numpy.seterr
方法捕获的适当警告/异常,例如numpy.seterr(all='raise')
我经常问提问者,形状是什么?dtype
?即使是类型
。跟踪这些属性是良好的numpy
编程的重要组成部分。即使是在MATLAB中,我发现获得正确的大小
也是调试的80%
类型
挤压
示例围绕类型
,ndarray
类与np.matrix
子类展开:
In [160]: np.squeeze(np.array([[1, 1]]))
Out[160]: array([1, 1])
In [161]: np.squeeze(np.matrix([[1, 1]]))
Out[161]: matrix([[1, 1]])
根据定义,矩阵对象总是二维的。这就是它如何重新定义ndarray
操作的核心
许多numpy
函数将其工作委托给方法。“挤压”的代码是:
try:
squeeze = a.squeeze
except AttributeError:
return _wrapit(a, 'squeeze')
try:
# First try to use the new axis= parameter
return squeeze(axis=axis)
except TypeError:
# For backwards compatibility
return squeeze()
所以[161]中的实际上是:
In [163]: np.matrix([[1, 1]]).squeeze()
Out[163]: matrix([[1, 1]])
np.matrix.squence
有自己的文档
作为一般规则,我们不鼓励使用np.matrix
。这是多年前为任性的MATLAB程序员创造的一个工具。在那些日子里,MATLAB只有2d矩阵(即使现在MATLAB的“标量”也是2d)
数据类型
np.array
是一个强大的函数。通常它的行为是直观的,但有时它会做出太多的假设
通常,它从输入中获取线索,无论是整数、浮点、字符串还是列表:
In [170]: np.array(1).dtype
Out[170]: dtype('int64')
In [171]: np.array(1.0).dtype
Out[171]: dtype('float64')
但它提供了许多参数。如果需要更多的控制,请使用这些选项:
array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)
In [173]: np.array(1, float).dtype
Out[173]: dtype('float64')
In [174]: np.array('1', float).dtype
Out[174]: dtype('float64')
In [177]: np.array('1', dtype=float,ndmin=2)
Out[177]: array([[1.]])
查看它的文档,以及列出许多其他数组创建函数的页面。看看他们的一些代码
例如np。至少\u 2d
进行了大量形状检查:
def atleast_2d(*arys):
res = []
for ary in arys:
ary = asanyarray(ary)
if ary.ndim == 0:
result = ary.reshape(1, 1)
elif ary.ndim == 1:
result = ary[newaxis,:]
else:
result = ary
res.append(result)
if len(res) == 1:
return res[0]
else:
return res
像这样的函数是防御性编程的好例子
我们得到了很多关于dtype=object
的一维数组的问题
In [272]: np.array([[1,2,3],[2,3]])
Out[272]: array([list([1, 2, 3]), list([2, 3])], dtype=object)
np.array
尝试使用统一的dtype
创建多维数组。但是,如果元素大小不同或者不能转换为相同的dtype
,它将返回到object
dtype。这是我们需要注意shape
和dtype
的情况之一
广播
广播一直是numpy
的一部分,没有办法关闭它。Octave和MATLAB后来添加了它,并且启用了警告开关
第一步是理解广播原则,即
- 它可以展开起始尺寸以匹配
- 它强制统一维度匹配
因此,一个基本的例子是:
In [180]: np.arange(3)[:,None] + np.arange(4)
Out[180]:
array([[0, 1, 2, 3],
[1, 2, 3, 4],
[2, 3, 4, 5]])
第一项是(3,)扩展到(3,1)。第二个是(4),通过广播扩展到(1,4)。(3,1)和(1,4)一起向(3,4)广播
许多numpy函数的参数使跟踪尺寸变得更容易。例如,sum
(和其他)有一个keepdims
参数:
In [181]: arr = _
In [182]: arr.sum(axis=0)
Out[182]: array([ 3, 6, 9, 12]) # (4,) shape
In [183]: arr.sum(axis=0,keepdims=True)
Out[183]: array([[ 3, 6, 9, 12]]) # (1,4) shape
In [184]: arr/_ # (3,4) / (1,4) => (3,4)
Out[184]:
array([[0. , 0.16666667, 0.22222222, 0.25 ],
[0.33333333, 0.33333333, 0.33333333, 0.33333333],
[0.66666667, 0.5 , 0.44444444, 0.41666667]])
在这种情况下,keepdims
不是必需的,因为(3,4)/(4,)可以工作。但是当轴=1时,形状变为(3,)而不能与(3,4)一起广播。但是(3,1)可以:
要管理形状,我喜欢:
- 调试时显示
形状
- 以交互方式测试代码段
- 使用诊断形状进行测试,例如
np.arange(24)。重塑(2,3,4)
In [272]: np.array([[1,2,3],[2,3]])
Out[272]: array([list([1, 2, 3]), list([2, 3])], dtype=object)
In [180]: np.arange(3)[:,None] + np.arange(4)
Out[180]:
array([[0, 1, 2, 3],
[1, 2, 3, 4],
[2, 3, 4, 5]])
In [181]: arr = _
In [182]: arr.sum(axis=0)
Out[182]: array([ 3, 6, 9, 12]) # (4,) shape
In [183]: arr.sum(axis=0,keepdims=True)
Out[183]: array([[ 3, 6, 9, 12]]) # (1,4) shape
In [184]: arr/_ # (3,4) / (1,4) => (3,4)
Out[184]:
array([[0. , 0.16666667, 0.22222222, 0.25 ],
[0.33333333, 0.33333333, 0.33333333, 0.33333333],
[0.66666667, 0.5 , 0.44444444, 0.41666667]])
In [185]: arr/arr.sum(axis=1,keepdims=True)
Out[185]:
array([[0. , 0.16666667, 0.33333333, 0.5 ],
[0.1 , 0.2 , 0.3 , 0.4 ],
[0.14285714, 0.21428571, 0.28571429, 0.35714286]])