Посмотрите на больше, чем на фигуру!
In [82]: a = np.array([1,1,1]) # Shape is (3,)
...: b = np.zeros((3,10,10)) # Shape is (3,10,10)
...: c = np.array([b[0], b[1], 0]) # Shape (3,)
In [83]:
In [83]: c
Out[83]:
array([array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]),
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]),
0], dtype=object)
In [84]: c.shape
Out[84]: (3,)
Да, c
просто имеет 3 элемента, но каждый является массивом или скаляром (последние 0).
In [85]: c-a
Out[85]:
array([array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]]),
array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]]),
-1], dtype=object)
Таким образом, вам удалось вычесть 1 из каждого из этих элементов!
c_wrong
- это совсем другой массив - это 3d с числовым типом d.Замена 0
на d[3]
делает все различия.
In [88]: c_wrong.shape
Out[88]: (3, 10, 10)
In [89]: c_wrong.dtype
Out[89]: dtype('float64')
Чтобы вычесть (3,) из (3, N, N), вы должны отрегулировать размеры a
до(3,1,1).Тогда он может сделать правильное вещание.
In [91]: c_wrong - a[:,None,None]
Out[91]:
array([[[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
....
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]]])
Я думаю, это просто случайность, что ваш c-a
работает.Определив c
с элементом 0
, вы создали массив object
dtype.Математика с объектными массивами dtype - это что-то среднее.Это вычитание является одним из тех хитов.Но не рассчитывай на это;есть много способов, которыми математика с таким массивом не работает - и она всегда медленнее.
c_wrong
по сути то же самое, что b
.
Ядро numpy - это многомерные числовые массивы.np.array
по умолчанию пытается построить настолько высокий размерный размер, насколько это возможно.В вашем c_wrong
случае он может сделать 3d;в c
это невозможно из-за скаляра 0. Поэтому он прибегает к созданию массива объектов 1d.
Самый верный способ создать массив объектов нужной формы - инициализировать «пробел».один, и заполните его.Но даже тогда заполнение может быть сложно.Здесь мне удалось сделать это с помощью:
In [92]: c3 = np.empty(3, object)
In [93]: c3
Out[93]: array([None, None, None], dtype=object)
In [94]: c3[:] = list(b)
In [95]: c3
Out[95]:
array([array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
....
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])], dtype=object)
In [96]: c3-a
Out[96]:
array([array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],
....
[-1., -1., -1., -1., -1., -1., -1., -1., -1., -1.]])], dtype=object)
Заполнение, которое не работает:
In [97]: c3[:] = b
------------------------------------------------------------------------
...
ValueError: could not broadcast input array from shape (3,10,10) into shape (3)
a[:,None,None]
не выглядит так ужасно, когда вы знакомы с вещанием.
Сравните время:
In [98]: timeit c_wrong-a[:,None,None]
5.22 µs ± 6.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [99]: timeit c3-a
9.53 µs ± 20.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [100]: timeit c-a
7.66 µs ± 10.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Или с dot
In [103]: timeit np.dot(a, b.reshape(3,-1)).shape
2.44 µs ± 9.63 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [104]: timeit np.dot(a,c).shape
10.9 µs ± 16.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [105]: timeit np.dot(a,c3).shape
11.6 µs ± 30.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
dot
имеет очень специфические правила - последняя ось a
должна совпадатьот второго до последнего из b
.Вот почему я использовал reshape
.И он передает задачу в быструю подпрограмму 'blas'.
С массивом объектов (3,) он выполняет произведение 1d dot
- но итеративно.
@
, matmul
работает с измененным b
, но не с c
или c3
.То же самое для einsum
: np.einsum('i,ijk->jk',a,b).shape
работает, но ничего, используя c
.