Подтвердить что ты не робот

Numpy: Почему не работает "a + = a.T"?

Как указано в лекционных записках scipy, это не сработает, как ожидалось:

a = np.random.randint(0, 10, (1000, 1000))
a += a.T
assert np.allclose(a, a.T)

Но почему? Как восприятие влияет на это поведение?

4b9b3361

Ответ 1

a += a.T

выполняет суммирование на месте (используя просмотр a.T во время обработки), поэтому вы получаете несимметричную матрицу вы можете легко проверить это, то есть я получил:

In [3]: a
Out[3]: 
array([[ 6, 15,  7, ...,  8,  9,  2],
       [15,  6,  9, ..., 14,  9,  7],
       [ 7,  9,  0, ...,  9,  5,  8],
       ..., 
       [ 8, 23, 15, ...,  6,  4, 10],
       [18, 13,  8, ...,  4,  2,  6],
       [ 3,  9,  9, ..., 16,  8,  4]])

Вы можете видеть, что это не симметрично, не так ли? (сравните элементы справа и слева)

если вы делаете реальную копию:

a += np.array(a.T)

он отлично работает, то есть:

In [6]: a
Out[6]: 
array([[ 2, 11,  8, ...,  9, 15,  5],
       [11,  4, 14, ..., 10,  3, 13],
       [ 8, 14, 14, ..., 10,  9,  3],
       ..., 
       [ 9, 10, 10, ..., 16,  7,  6],
       [15,  3,  9, ...,  7, 14,  1],
       [ 5, 13,  3, ...,  6,  1,  2]])

Чтобы лучше понять, почему это так, вы можете себе представить, что вы сами пишете цикл:

In [8]: for i in xrange(1000):
            for j in xrange(1000):
                a[j,i] += a[i,j]
   ....:         

In [9]: a
Out[9]: 
array([[ 4,  5, 14, ..., 12, 16, 13],
       [ 3,  2, 16, ..., 16,  8,  8],
       [ 9, 12, 10, ...,  7,  7, 23],
       ..., 
       [ 8, 10,  6, ..., 14, 13, 23],
       [10,  4,  6, ...,  9, 16, 21],
       [11,  8, 14, ..., 16, 12, 12]])

Он добавляет [999,0] для вычисления [0,999], но [999,0] уже имеет сумму [999,0] + a [0,999] - поэтому ниже основной диагонали вы добавляете значения в два раза.

Ответ 2

Эта проблема связана с внутренними конструкциями numpy. В основном это сводится к тому, что оператор inplace будет изменять значения по мере их появления, а затем эти измененные значения будут использоваться там, где вы действительно намеревались использовать исходное значение.

Это обсуждается в этот отчет об ошибках, и он не кажется исправляемым.

Причина, по которой он работает для массивов меньшего размера, по-видимому, объясняется тем, как данные буферизуются при работе.

Чтобы точно понять, почему проблема возникает, я боюсь, вам придется копать внутренности numpy.

Ответ 3

assert np.allclose(a, a.T)

Теперь я понял, что вы генерируете симметричную матрицу, суммируя a с ее передачей a.T, приводящей к симметричной матрице)

Это заставляет нас по праву ожидать, что np.allclose(a, a.T) вернет true (результирующая матрица будет симметричной, поэтому она должна быть равна его транспонированию)

a + = a.T # Как представление влияет на это поведение?

Я только что сузил его до этого

TL; DR a = a + a.T отлично подходит для больших матриц, а a += a.T дает странные результаты, начиная с размера 91x91

>>> a = np.random.randint(0, 10, (1000, 1000))
>>> a += a.T  # using the += operator
>>> np.allclose(a, a.T)
False
>>> a = np.random.randint(0, 10, (1000, 1000))
>>> a = a + a.T # using the + operator
>>> np.allclose(a, a.T)
True

У меня такое же обрезание размером 90х90, как @Hannes (я на Python 2.7 и Numpy 1.11.0, поэтому есть как минимум две среды, которые могут это сделать)

>>> a = np.random.randint(0, 10, (90, 90))
>>> a += a.T
>>> np.allclose(a, a.T)
True
>>> a = np.random.randint(0, 10, (91, 91))
>>> a += a.T
>>> np.allclose(a, a.T)
False

Ответ 4

Для больших массивов оператор на месте заставляет вас применять дополнение к оператору, который в настоящее время работает. Например:

>>> a = np.random.randint(0, 10, (1000, 1000))
>>> a
array([[9, 4, 2, ..., 7, 0, 6],
       [8, 4, 1, ..., 3, 5, 9],
       [6, 4, 9, ..., 6, 9, 7],
       ..., 
       [6, 2, 5, ..., 0, 4, 6],
       [5, 7, 9, ..., 8, 0, 5],
       [2, 0, 1, ..., 4, 3, 5]])

Обратите внимание, что элементы верхнего и нижнего левого края - 6 и 2.

>>> a += a.T
>>> a
array([[18, 12,  8, ..., 13,  5,  8],
       [12,  8,  5, ...,  5, 12,  9],
       [ 8,  5, 18, ..., 11, 18,  8],
       ..., 
       [19,  7, 16, ...,  0, 12, 10],
       [10, 19, 27, ..., 12,  0,  8],
       [10,  9,  9, ..., 14, 11, 10]])

Теперь обратите внимание, что правый верхний элемент правильный (8 = 6 + 2). Однако нижний левый элемент является результатом не 6 + 2, а 8 + 2. Другими словами, добавление, которое было применено к нижнему левому элементу, является верхним правым элементом массива после добавления. Вы заметите, что это верно для всех остальных элементов ниже первой строки.

Я предполагаю, что это работает так, потому что вам не нужно делать копию вашего массива. (Хотя, похоже, он делает копию, если массив мал.)