NumPy数组的原地类型转换

140

给定一个 int32 的 NumPy 数组,如何原地将其转换为 float32?也就是说,我想要做到:

a = a.astype(numpy.float32)

不复制数组。它很大。

这样做的原因是我有两个计算 a 的算法,其中一个返回一个 int32 数组,另一个返回一个 float32 数组(这与两个不同的算法固有相关性)。所有进一步的计算都假定 a 是一个 float32 数组。

目前我通过 ctypes 调用 C 函数进行转换。有没有办法在 Python 中完成这个工作?


4
@Karl:不,因为我必须自己编写和编译C函数。 - Sven Marnach
天真的问题:如何判断a=a.astype(numpy.float32)是否在进行复制?Python变得非常缓慢,你的磁盘开始疯狂运转? - Andrew
4
@Andrew:有很多方法可以判断它是否返回一个副本。其中一种方法是阅读文档。 (http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.astype.html) - Sven Marnach
你认为使用a = np.cast ['f'](a)和a你的int32数组有什么想法? - Stefano Messina
1
就地(in-place)简单地意味着“使用与原始数组相同的内存”。请查看已接受的答案--最后一部分显示新值确实已经覆盖了相同的内存。 - Sven Marnach
显示剩余4条评论
7个回答

167

更新:如果有可能避免复制,此函数只会避免复制,因此这不是此问题的正确答案。unutbu 的回答才是正确的答案。


a = a.astype(numpy.float32, copy=False)

numpy astype 有一个复制标志(copy flag)。为什么我们不应该使用它?


15
一旦这个参数在NumPy发布中得到支持,我们当然可以使用它,但目前它只能在开发分支中使用。而在我提问的那个时间,它根本不存在。 - Sven Marnach
3
现在已经支持了,至少在我的版本(1.7.1)中支持。 - PhilMacKay
它似乎在Python3.3中与最新的numpy版本完美地工作。 - CHM
1
我发现这个比a = a.view((float, len(a.dtype.names)))慢了大约700倍。 - J.J
16
复制标志只是表示如果更改可以在不进行复制的情况下完成,那么就会在不进行复制的情况下完成。但是,如果类型不同,它仍然会始终进行复制。 - coderforlife
1
import numpy as np; x = np.ones(int(1.9e9), dtype=np.int64); x.astype(np.float64, copy=False) 在一台内存为16 Gb的机器上会出现内存不足的错误。它可能仍然会创建中间文件。 - hamster on wheels

118

您可以创建一个具有不同dtype的视图,然后将其原地复制到该视图中:

import numpy as np
x = np.arange(10, dtype='int32')
y = x.view('float32')
y[:] = x

print(y)

产量
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.], dtype=float32)

为了展示转换是原地进行的,需要注意从 x 复制到 y 会改变 x 的值。
print(x)

打印

array([         0, 1065353216, 1073741824, 1077936128, 1082130432,
       1084227584, 1086324736, 1088421888, 1090519040, 1091567616])

33
对于那些希望在不同字节大小的dtype之间进行转换(例如32位到16位)的人(像我一样),需要注意以下内容:此方法会失败,因为y.size <> x.size。一旦你想到这点就很容易理解了 :-( - Juh_
这个解决方案在旧版本的Numpy中是否有效?当我在Numpy 1.8.2上执行np.arange(10, dtype=np.int32).view(np.float32)时,我得到了array([ 0.00000000e+00, 1.40129846e-45, ... [snip] ... 1.26116862e-44], dtype=float32) - Bas Swinckels
3
这是预期的。当你执行y[:] = x时,就会发生转换。 - unutbu
为了澄清原回答和@Juh_所提到的itemsize(位数),例如: a = np.arange(10, dtype='float32'); b = a[::-1]; c = np.vstack((a,b)); d = c.view('float64') 这段代码使用了10个float32,得到的结果是10个float64,而不是20个。 - dcanelhas
2
这种原地更改可能会节省内存使用,但它比简单的 x.astype(float) 转换要慢。除非你的脚本接近 MemoryError,否则我不建议使用它。 - hpaulj

14

你可以通过以下方式更改数组类型而不进行转换:

a.dtype = numpy.float32

但首先,您需要将所有的整数更改为将被解释为相应浮点数的内容。一个非常缓慢的方法是使用 Python 的 struct 模块,像这样:

def toi(i):
    return struct.unpack('i',struct.pack('f',float(i)))[0]

应用于您的数组的每个成员。

但也许更快的方法是利用numpy的ctypeslib工具(我不熟悉)。

- 编辑 -

由于ctypeslib似乎不起作用,那么我将使用典型的numpy.astype方法进行转换,但是分块大小应该在您的内存限制范围内。

a[0:10000] = a[0:10000].astype('float32').view('int32')

然后在完成后更改dtype。

这里有一个函数,可以为任何兼容的dtype(仅适用于具有相同大小项的dtype)完成该任务,并处理形状任意的数组,并使用户可以控制块大小:

import numpy

def astype_inplace(a, dtype, blocksize=10000):
    oldtype = a.dtype
    newtype = numpy.dtype(dtype)
    assert oldtype.itemsize is newtype.itemsize
    for idx in xrange(0, a.size, blocksize):
        a.flat[idx:idx + blocksize] = \
            a.flat[idx:idx + blocksize].astype(newtype).view(oldtype)
    a.dtype = newtype

a = numpy.random.randint(100,size=100).reshape((10,10))
print a
astype_inplace(a, 'float32')
print a

2
感谢您的回答。老实说,我认为这对于大型数组并不是非常有用——它太慢了。将数组的数据重新解释为不同类型很容易——例如通过调用a.view(numpy.float32)。困难的部分实际上是转换数据。numpy.ctypeslib只能帮助重新解释数据,而不能实际转换数据。 - Sven Marnach
好的。我不确定您的内存/处理器限制是什么。请查看我的编辑。 - Paul
感谢更新。分块处理是一个好主意,这可能是当前NumPy接口下最好的解决方案。但在这种情况下,我可能会坚持使用我的当前ctypes解决方案。 - Sven Marnach

0

读取数据所花费的时间

t1=time.time() ; V=np.load ('udata.npy');t2=time.time()-t1 ; print( t2 )

95.7923333644867

V.dtype

数据类型('>f8')

V.shape

(3072,1024,4096)

**创建新数组**

t1=time.time() ; V64=np.array( V, dtype=np.double); t2=time.time()-t1 ; print( t2 )

1291.669689655304

简单的原地numpy转换

t1=time.time() ; V64=np.array( V, dtype=np.double); t2=time.time()-t1 ; print( t2 )

205.64322113990784

使用 astype

t1=time.time() ; V = V.astype(np.double) ; t2=time.time()-t1 ; print( t2 )

400.6731758117676

使用视图

t1=time.time() ; x=V.view(np.double);V[:,:,:]=x ;t2=time.time()-t1 ; print( t2 )

556.5982494354248

请注意,每次我都清除了变量。因此,让Python处理转换是最有效的方法。

有几个原因使我想避免复制。主要原因是它使用了两倍的内存,而这通常根本装不下。另一个原因是我需要数组指针保持稳定,因为还有一些C代码在保留同样的指针。原始速度是一个问题,但不是主要问题。我意识到其他人访问这个问题可能有其他要求,所以对于他们来说,拥有这些基准测试可能是有用的。 - Sven Marnach
1
错误: "创建新数组" 和 "简单的原地numpy转换" 看起来相同。请纠正。@Arphy - Gokul NC

-1
import numpy as np
arr_float = np.arange(10, dtype=np.float32)
arr_int = arr_float.view(np.float32)

使用 view() 函数和参数 'dtype' 可以原地改变数组。

这个问题的目标实际上是在原地“转换”数据。在将最后一行中的类型更正为“int”之后,此答案只会将现有数据重新解释为不同的类型,这不是我要求的。 - Sven Marnach
你的意思是什么?dtype只是数据在内存中的外观,它确实起作用。但是在np.astype中,参数'casting'可以控制转换方法,默认为'unsafe'。 - 蒋志强
是的,我同意第一个被接受的答案。然而,arr_.astype(new_dtype, copy=False) 仍会返回一个新分配的数组。如何满足 dtypeordersubok 的要求以返回数组的副本?我无法解决它。 - 蒋志强

-5

a = np.subtract(a, 0., dtype=np.float32)


1
虽然这段代码片段可能是解决方案,但包括解释真的有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您的代码建议原因。 - Sebastialonso
为什么这应该是一个“原地”转换?numpy.subtract不是返回一个副本吗?只有名称a被重用于另一块数据……如果我对此有误,请解释一下。 - koffein
谢谢你指出了这个问题,看起来你是正确的——确实产生了一个副本。 - MIO

-5
使用这个:
In [105]: a
Out[105]: 
array([[15, 30, 88, 31, 33],
       [53, 38, 54, 47, 56],
       [67,  2, 74, 10, 16],
       [86, 33, 15, 51, 32],
       [32, 47, 76, 15, 81]], dtype=int32)

In [106]: float32(a)
Out[106]: 
array([[ 15.,  30.,  88.,  31.,  33.],
       [ 53.,  38.,  54.,  47.,  56.],
       [ 67.,   2.,  74.,  10.,  16.],
       [ 86.,  33.,  15.,  51.,  32.],
       [ 32.,  47.,  76.,  15.,  81.]], dtype=float32)

5
你确定那不是一份复制品吗?你能检查一下并稍微解释一下吗? - Michele d'Amico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接