将 NumPy 数组重新解释为不同的 dtype
Reinterpreting NumPy arrays as a different dtype
假设我有一个大型 NumPy 数组 dtype
int32
import numpy as np
N = 1000 # (large) number of elements
a = np.random.randint(0, 100, N, dtype=np.int32)
但现在我希望数据为 uint32
。我可以
b = a.astype(np.uint32)
甚至
b = a.astype(np.uint32, copy=False)
但在这两种情况下 b
都是 a
的副本,而我想简单地将 a
中的数据重新解释为 uint32
,而不是重复记忆。同样,使用 np.asarray()
也无济于事。
的作用是
a.dtpye = np.uint32
这只是简单地改变了 dtype
而根本没有改变数据。这是一个引人注目的例子:
import numpy as np
a = np.array([-1, 0, 1, 2], dtype=np.int32)
print(a)
a.dtype = np.uint32
print(a) # shows "overflow", which is what I want
我的问题是关于简单覆盖数组的dtype
的解决方案:
- 这是合法的吗?你能告诉我这个功能在哪里记录吗?
- 它实际上是否使数组的数据保持不变,即没有重复数据?
- 如果我想要两个数组
a
和 b
共享相同的数据,但将其视为不同的 dtype
怎么办?我发现以下方法有效,但我再次担心这样做是否真的可行:
import numpy as np
a = np.array([0, 1, 2, 3], dtype=np.int32)
b = a.view(np.uint32)
print(a) # [0 1 2 3]
print(b) # [0 1 2 3]
a[0] = -1
print(a) # [-1 1 2 3]
print(b) # [4294967295 1 2 3]
尽管这似乎可行,但我发现这两个数组的基础 data
似乎并不位于内存中的同一位置,这很奇怪:
print(a.data)
print(b.data)
其实上面好像每次都是不同的结果运行,所以我完全不明白那里是怎么回事。
- 这可以扩展到其他
dtype
s,其中最极端的可能是混合 32 位和 64 位浮点数:
import numpy as np
a = np.array([0, 1, 2, np.pi], dtype=np.float32)
b = a.view(np.float64)
print(a) # [0. 1. 2. 3.1415927]
print(b) # [0.0078125 50.12387848]
b[0] = 8
print(a) # [0. 2.5 2. 3.1415927]
print(b) # [8. 50.12387848]
再一次,如果获得的行为真的是我所追求的,这是纵容吗?
- Is this legitimate? Can you point me to where this feature is documented?
这是合法的。但是,使用 np.view
(等效)更好,因为它与静态分析器兼容(因此它在某种程度上更安全)。事实上,documentation 指出:
It’s possible to mutate the dtype
of an array at runtime. [...]
This sort of mutation is not allowed by the types. Users who want to write statically typed code should instead use the numpy.ndarray.view
method to create a view of the array with a different dtype
.
- Does it in fact leave the data of the array untouched, i.e. no duplication of the data?
是的。由于数组仍然是同一内部内存缓冲区(基本字节数组)上的 view。 Numpy 只是重新解释不同而已(这是直接把每个 Numpy 计算函数的 C 代码搞定了)。
- What if I want two arrays
a
and b
sharing the same data, but view it as different dtypes
? [...]
np.view
可以在这种情况下使用,就像您在示例中所做的那样。但是,结果 平台相关 。事实上,Numpy 只是 reinterpret bytes of memory and theoretically the representation of negative numbers can change from one machine to another. Hopefully, nowadays, all mainstream modern processors use use the two's complement (source)。这意味着像 -1
这样的 np.in32
值将被重新解释为具有类型 np.uint32
的 2**32-1 = 4294967295
。正符号值不变。只要您意识到这一点,就可以了,而且行为是可以预见的。
- This can be extended to other
dtypes
, the most extreme of which is probably mixing 32 and 64 bit floats.
好吧,简而言之,这真的是在玩火。在这种情况下,这肯定 不安全 尽管它可能适用于您的特定机器。让我们浑水摸鱼
首先,np.view
的文档指出:
The behavior of the view cannot be predicted just from the superficial appearance of a
. It also depends on exactly how a
is stored in memory. Therefore if a
is C-ordered versus fortran-ordered, versus defined as a slice or transpose, etc., the view may give different results.
问题是 Numpy 使用 C 代码重新解释了指针。因此,据我所知,strict aliasing rule 适用。这意味着将 np.float32
值重新解释为 np.float64
会导致 未定义的行为 。一个原因是 np.float32
(通常为 4)和 np.float32
(通常为 8)的对齐要求不同,因此从内存中读取未对齐的 np.float64
值 可以在某些体系结构(例如 POWER)上导致崩溃 尽管 x86-64 处理器支持此操作。另一个原因来自编译器,由于严格的别名规则,编译器可以通过在您的情况下做出错误的假设来 over-optimize 代码(例如 np.float32
值和 np.float64
值不能在内存中重叠,所以视图的修改不应更改原始数组)。然而,由于 Numpy 是从 CPython 调用的,并且没有函数调用是从解释器内联的(可能不是 Cython),最后一点应该不是问题(如果你使用 Numba 或任何 JIT 可能就是这种情况)。请注意,获得 np.float32
的 np.uint8
视图是安全的,因为它不会违反严格的别名规则(并且对齐是正确的)。这对于高效序列化 Numpy 数组很有用。相反的操作是不安全的(特别是由于对齐)。
关于最后一节的更新: 对 Numpy 代码的更深入分析表明,type-conversion functions perform a safe type punning using the memmove
C call, while some other functions like all basic unary operators or binary ones 等代码的某些部分似乎还没有进行适当的类型双关!此外,这样的功能几乎没有被用户测试,棘手的极端情况可能会导致奇怪的错误(特别是如果你在两个视图中读写同一个数组)。因此,使用它需要您自担风险。
假设我有一个大型 NumPy 数组 dtype
int32
import numpy as np
N = 1000 # (large) number of elements
a = np.random.randint(0, 100, N, dtype=np.int32)
但现在我希望数据为 uint32
。我可以
b = a.astype(np.uint32)
甚至
b = a.astype(np.uint32, copy=False)
但在这两种情况下 b
都是 a
的副本,而我想简单地将 a
中的数据重新解释为 uint32
,而不是重复记忆。同样,使用 np.asarray()
也无济于事。
的作用是
a.dtpye = np.uint32
这只是简单地改变了 dtype
而根本没有改变数据。这是一个引人注目的例子:
import numpy as np
a = np.array([-1, 0, 1, 2], dtype=np.int32)
print(a)
a.dtype = np.uint32
print(a) # shows "overflow", which is what I want
我的问题是关于简单覆盖数组的dtype
的解决方案:
- 这是合法的吗?你能告诉我这个功能在哪里记录吗?
- 它实际上是否使数组的数据保持不变,即没有重复数据?
- 如果我想要两个数组
a
和b
共享相同的数据,但将其视为不同的dtype
怎么办?我发现以下方法有效,但我再次担心这样做是否真的可行:
尽管这似乎可行,但我发现这两个数组的基础import numpy as np a = np.array([0, 1, 2, 3], dtype=np.int32) b = a.view(np.uint32) print(a) # [0 1 2 3] print(b) # [0 1 2 3] a[0] = -1 print(a) # [-1 1 2 3] print(b) # [4294967295 1 2 3]
data
似乎并不位于内存中的同一位置,这很奇怪:
其实上面好像每次都是不同的结果运行,所以我完全不明白那里是怎么回事。print(a.data) print(b.data)
- 这可以扩展到其他
dtype
s,其中最极端的可能是混合 32 位和 64 位浮点数:
再一次,如果获得的行为真的是我所追求的,这是纵容吗?import numpy as np a = np.array([0, 1, 2, np.pi], dtype=np.float32) b = a.view(np.float64) print(a) # [0. 1. 2. 3.1415927] print(b) # [0.0078125 50.12387848] b[0] = 8 print(a) # [0. 2.5 2. 3.1415927] print(b) # [8. 50.12387848]
- Is this legitimate? Can you point me to where this feature is documented?
这是合法的。但是,使用 np.view
(等效)更好,因为它与静态分析器兼容(因此它在某种程度上更安全)。事实上,documentation 指出:
It’s possible to mutate the
dtype
of an array at runtime. [...] This sort of mutation is not allowed by the types. Users who want to write statically typed code should instead use thenumpy.ndarray.view
method to create a view of the array with a differentdtype
.
- Does it in fact leave the data of the array untouched, i.e. no duplication of the data?
是的。由于数组仍然是同一内部内存缓冲区(基本字节数组)上的 view。 Numpy 只是重新解释不同而已(这是直接把每个 Numpy 计算函数的 C 代码搞定了)。
- What if I want two arrays
a
andb
sharing the same data, but view it as differentdtypes
? [...]
np.view
可以在这种情况下使用,就像您在示例中所做的那样。但是,结果 平台相关 。事实上,Numpy 只是 reinterpret bytes of memory and theoretically the representation of negative numbers can change from one machine to another. Hopefully, nowadays, all mainstream modern processors use use the two's complement (source)。这意味着像 -1
这样的 np.in32
值将被重新解释为具有类型 np.uint32
的 2**32-1 = 4294967295
。正符号值不变。只要您意识到这一点,就可以了,而且行为是可以预见的。
- This can be extended to other
dtypes
, the most extreme of which is probably mixing 32 and 64 bit floats.
好吧,简而言之,这真的是在玩火。在这种情况下,这肯定 不安全 尽管它可能适用于您的特定机器。让我们浑水摸鱼
首先,np.view
的文档指出:
The behavior of the view cannot be predicted just from the superficial appearance of
a
. It also depends on exactly howa
is stored in memory. Therefore ifa
is C-ordered versus fortran-ordered, versus defined as a slice or transpose, etc., the view may give different results.
问题是 Numpy 使用 C 代码重新解释了指针。因此,据我所知,strict aliasing rule 适用。这意味着将 np.float32
值重新解释为 np.float64
会导致 未定义的行为 。一个原因是 np.float32
(通常为 4)和 np.float32
(通常为 8)的对齐要求不同,因此从内存中读取未对齐的 np.float64
值 可以在某些体系结构(例如 POWER)上导致崩溃 尽管 x86-64 处理器支持此操作。另一个原因来自编译器,由于严格的别名规则,编译器可以通过在您的情况下做出错误的假设来 over-optimize 代码(例如 np.float32
值和 np.float64
值不能在内存中重叠,所以视图的修改不应更改原始数组)。然而,由于 Numpy 是从 CPython 调用的,并且没有函数调用是从解释器内联的(可能不是 Cython),最后一点应该不是问题(如果你使用 Numba 或任何 JIT 可能就是这种情况)。请注意,获得 np.float32
的 np.uint8
视图是安全的,因为它不会违反严格的别名规则(并且对齐是正确的)。这对于高效序列化 Numpy 数组很有用。相反的操作是不安全的(特别是由于对齐)。
关于最后一节的更新: 对 Numpy 代码的更深入分析表明,type-conversion functions perform a safe type punning using the memmove
C call, while some other functions like all basic unary operators or binary ones 等代码的某些部分似乎还没有进行适当的类型双关!此外,这样的功能几乎没有被用户测试,棘手的极端情况可能会导致奇怪的错误(特别是如果你在两个视图中读写同一个数组)。因此,使用它需要您自担风险。