首页 > 解决方案 > ValueError:当更改为更大的 dtype 时,其大小必须是数组最后一个轴的总大小(以字节为单位)的除数

问题描述

在第三方提供的 numpy 数据集上运行时,我遇到了以下异常:

ValueError: When changing to a larger dtype, its size must be a divisor of the total size in bytes of the last axis of the array

在什么情况下numpy会提出这个问题?我的代码正在对 numpy 数组应用一个视图,我试图在其中应用一个dtype与行中元素数匹配的结构。

X.view([('', X.dtype)] * X.shape[1])当在函数内部调用语句时,我看到此错误f- 但不是在每次调用此函数时f

ipdb> X.view([('', X.dtype)] * X.shape[1])
*** ValueError: When changing to a larger dtype, its size must be a divisor of the total size in bytes of the last axis of the array.

X始终是具有两个轴的数组(len(X.shape)始终为 2),因此您会期望一个长的结构dtypeX.shape[1]适合最后一个轴(X.shape[1])。

并非所有数据集都发生异常,那么是什么导致 numpy 为某些数组而不是其他数组抛出此异常?我什至看不到哪个 .py numpy 源代码抛出了这个错误。

我发现很难为此生成一个 MCVE,但我已将其缩小到一个colab 笔记本,它仍然有点大,可以在这里发布。

这里X应该是iris我从 scikit learn 得到的数据集的一个子集。

from sklearn.datasets import load_iris
X = load_iris().data

我的代码如下所示:

def f(X):
    X_rows = X.view([('', X.dtype)] * X.shape[1])

def g(X):
    f(X)

def h(X):
    f(X)

# call the functions
g(X) # this runs without a problem
f(X) # this returns the error

标签: pythonpython-3.xnumpy

解决方案


您正在尝试在具有不兼容内存布局的数组上创建视图,其中输出dtype项目大小不完全适合内存中覆盖源数组“最后一个”轴的全长所需的字节数. 如果您只是直接.dtype在数组上设置属性,则例外也适用,而不仅仅是(在该新对象上创建一个带有set 的新对象)。ndarray.view()ndarraydtype

这里的“最后一个”轴是内存布局方面的“最里面”维度;对于 C 阶数组是shape[-1],对于 Fortran 阶数组是shape[0]. 该维度大小乘以原始大小dtype.itemsize必须可以被 new 整除dtype.itemsize,否则您无法干净地“遍历”内部存储器结构。

例如,对于形状(4, 3, 5)和 adtype.itemsize为 8 的 C 顺序(行主顺序)数组,“最后一个”轴占用 5 * 8 == 40 字节的内存,因此您可以在此创建一个更大的视图大小为 10、20 和 40 的 dtype。但是,相同的数组但按Fortran顺序(列优先顺序)使用 4 * 8 == 32 字节的内存,将您的选项限制为仅大小为 16 和 32 的更大 dtype。

如果X.view([('', X.dtype)] * X.shape[1])失败,则要么X.shape具有比 2 更多的维度,要么是使用 Fortran 排序的数组。您可以通过使用更正第一个X.shape[-1],您可以通过查看来检查 lattr ndarray.flags['F_CONTIGUOUS']。将这些组合成一个表达式,如下所示:

X_rows = X.view([('', X.dtype)] * X.shape[0 if X.flags['F_CONTIGUOUS'] else -1])

但是,正如ndarray.view()文档警告的那样:

dtype通常应避免在由切片、转置、fortran 排序等定义的数组上使用更改大小(每个条目的字节数)的视图。[.]

当您尝试更改 Fortran 顺序数组的 dtype 时,会引发警告:

DeprecationWarning: Changing the shape of an F-contiguous array by descriptor assignment is deprecated. To maintain the Fortran contiguity of a multidimensional Fortran array, use 'a.T.view(...).T' instead

所以最好转置数组,创建视图,然后再次转置结果视图:

if X.flags['F_CONTIGUOUS']:
    X_rows = X.T.view([('', X.dtype)] * X.shape[0]).T

你仍然需要坚持X.shape[0]这里,那是shape[-1]转置数组。

不赞成更改对 Fortran 顺序数组的支持这一事实dtype也可以解释异常对“最后一个轴”的引用,这在 C 顺序数组方面是非常自然的,但在应用于 Fortran 顺序数组时感觉违反直觉.

我什至看不到哪个 .py numpy 源代码抛出了这个错误。

Numpy 主要是用 C 语言编写的(带有少量 Fortran 77),因此您需要深入研究已编译组件的源代码。dtype错误在描述符设置器函数中引发,这里在PyArray_View()函数调用PyObject_SetAttrString()函数以设置dtype属性时调用该函数,当从ndarray.view()方法调用它时。

根据源代码,不仅不推荐更改 Fortran 顺序数组的 dtype,而且根本不支持非连续数组的视图(这意味着如果两者都是X.flags['C_CONTIGUOUS'],那么您根本无法更改)。X.flags['F_CONTIGUOUS']Falsedtype


推荐阅读