Вы столкнулись с известной проблемой .numpy.zeros
требует кортежа при создании многомерного массива.Если вы передаете что-то, кроме кортежа, это иногда работает, но это только потому, что numpy
умен, чтобы сначала преобразовать объект в кортеж.
Проблема в том, что numba
в настоящее время не поддерживает преобразование произвольных итераций в кортежи .Так что эта строка не работает, когда вы пытаетесь скомпилировать ее в режиме nopython=True
.(Несколько других тоже терпят неудачу, но это первое.)
Out=np.zeros(dimN)
Теоретически вы можете вызвать np.prod(dimN)
, создать плоский массив нулей и изменить его, но затем вы столкнетесь ста же проблема: метод reshape
для массивов numpy
требует кортежа!
Это довольно неприятная проблема с numba
- я не сталкивался с этим раньше.Я действительно сомневаюсь, что решение, которое я нашел, является правильным, но это рабочее решение, которое позволяет нам скомпилировать версию в режиме nopython=True
.
Основная идея состоит в том, чтобы избежать использования кортежей для индексации путем непосредственной реализации индексатора, который следует за strides
массива:
@nb.jit(nopython=True)
def index_arr(a, ix_arr):
strides = np.array(a.strides) / a.itemsize
ix = int((ix_arr * strides).sum())
return a.ravel()[ix]
@nb.jit(nopython=True)
def index_set_arr(a, ix_arr, val):
strides = np.array(a.strides) / a.itemsize
ix = int((ix_arr * strides).sum())
a.ravel()[ix] = val
Это позволяет нам получать и устанавливать значения без необходимостикортеж
Мы также можем избежать использования reshape
, передав выходной буфер в функцию joted и обернув эту функцию в помощник:
@nb.jit() # We can't use nopython mode here...
def mytensordot(A, B, iA, iB):
iA, iB = np.array(iA, dtype=np.int32), np.array(iB, dtype=np.int32)
dimA, dimB = A.shape, B.shape
NdimA, NdimB = len(dimA), len(dimB)
if len(iA) != NdimA:
raise ValueError("iA must be same size as dim A")
if len(iB) != NdimB:
raise ValueError("iB must be same size as dim B")
NdimN = NdimA + NdimB
dimN = np.zeros(NdimN, dtype=np.int32)
dimN[iA] = dimA
dimN[iB] = dimB
Out = np.zeros(dimN)
return mytensordot_jit(A, B, iA, iB, dimN, Out)
Поскольку помощник не содержит циклов, он добавляетнекоторые накладные расходы, но накладные расходы довольно тривиальны.Вот последняя заключенная в кавычки функция:
@nb.jit(nopython=True)
def mytensordot_jit(A, B, iA, iB, dimN, Out):
for i in range(np.prod(dimN)):
nidxs = int_to_idx(i, dimN)
a = index_arr(A, nidxs[iA])
b = index_arr(B, nidxs[iB])
index_set_arr(Out, nidxs, a * b)
return Out
К сожалению, это не приводит к такому ускорению, как нам хотелось бы.На массивах это примерно в 5 раз медленнее, чем tensordot
;на больших массивах он все еще в 50 раз медленнее.(Но, по крайней мере, это не в 1000 раз медленнее!) В ретроспективе это не слишком удивительно, поскольку dot
и tensordot
оба используют BLAS под капотом, так как @hpaulj напоминает нам .
Закончив этот код, я увидел, что einsum
решил вашу настоящую проблему - приятно!
Но основная проблема, на которую указывает ваш первоначальный вопрос - что индексация с кортежами произвольной длины невозможна в объединенном коде - все еще вызывает разочарование.Надеюсь, это будет полезно кому-то еще!