ValueError:更改为较大的dtype时,其大小必须是数组最后一个轴的总大小(以字节为单位)的除数

J. *_*Doe 5 python numpy python-3.x

在第三方提供的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
Run Code Online (Sandbox Code Playgroud)

在什么情况下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.
Run Code Online (Sandbox Code Playgroud)

X总是有两个轴(数组len(X.shape)始终是2),所以你会期望一个结构dtypeX.shape[1]长期适应的最后一个轴(X.shape[1])。

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

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

这里X应该是iris我从scikit学习获得的数据集的子集。

from sklearn.datasets import load_iris
X = load_iris().data
Run Code Online (Sandbox Code Playgroud)

我的代码如下所示:

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
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 4

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

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

例如,对于形状(4, 3, 5)dtype.itemsize8 的 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])
Run Code Online (Sandbox Code Playgroud)

然而,正如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
Run Code Online (Sandbox Code Playgroud)

因此最好转置数组,创建视图,然后再次转置生成的视图:

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

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

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

我什至看不到哪个 .py numpy 源代码引发了此错误。

Numpy 主要是用 C 编写的(带有一点 Fortran 77),因此您需要深入研究已编译组件的源代码。该错误是在dtype描述符 setter 函数中引发的,在从方法调用该函数时,当该PyArray_View()函数调用该PyObject_SetAttrString()函数来设置属性时,会调用该函数。dtypendarray.view()

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