Как указано в лекционных записках scipy, это не сработает, как ожидалось:
a = np.random.randint(0, 10, (1000, 1000))
a += a.T
assert np.allclose(a, a.T)
Но почему? Как восприятие влияет на это поведение?
Как указано в лекционных записках scipy, это не сработает, как ожидалось:
a = np.random.randint(0, 10, (1000, 1000))
a += a.T
assert np.allclose(a, a.T)
Но почему? Как восприятие влияет на это поведение?
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] - поэтому ниже основной диагонали вы добавляете значения в два раза.
Эта проблема связана с внутренними конструкциями numpy. В основном это сводится к тому, что оператор inplace будет изменять значения по мере их появления, а затем эти измененные значения будут использоваться там, где вы действительно намеревались использовать исходное значение.
Это обсуждается в этот отчет об ошибках, и он не кажется исправляемым.
Причина, по которой он работает для массивов меньшего размера, по-видимому, объясняется тем, как данные буферизуются при работе.
Чтобы точно понять, почему проблема возникает, я боюсь, вам придется копать внутренности numpy.
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
Для больших массивов оператор на месте заставляет вас применять дополнение к оператору, который в настоящее время работает. Например:
>>> 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. Другими словами, добавление, которое было применено к нижнему левому элементу, является верхним правым элементом массива после добавления. Вы заметите, что это верно для всех остальных элементов ниже первой строки.
Я предполагаю, что это работает так, потому что вам не нужно делать копию вашего массива. (Хотя, похоже, он делает копию, если массив мал.)