Почему Python копирует массивы NumPy, где длина измерений одинакова? - PullRequest
0 голосов
/ 19 февраля 2019

У меня проблема со ссылкой на массив NumPy.У меня есть массив вида

import numpy as np
a = [np.array([0.0, 0.2, 0.4, 0.6, 0.8]),
     np.array([0.0, 0.2, 0.4, 0.6, 0.8]),
     np.array([0.0, 0.2, 0.4, 0.6, 0.8])]

Если я сейчас создам новую переменную,

b = np.array(a)

и сделаю

b[0] += 1
print(a)

, тогда a неменяется.

a = [array([0. , 0.2, 0.4, 0.6, 0.8]),
     array([0. , 0.2, 0.4, 0.6, 0.8]),
     array([0. , 0.2, 0.4, 0.6, 0.8])]

Но если я сделаю то же самое с:

a = [np.array([0.0, 0.2, 0.4, 0.6, 0.8]),
     np.array([0.0, 0.2, 0.4, 0.6, 0.8]),
     np.array([0.0, 0.2, 0.4, 0.6])]

, поэтому я удалил одно число в конце последнего измерения.Затем я делаю это снова:

b = np.array(a)
b[0] += 1
print(a)

Теперь a меняется, я думал, что это нормальное поведение в Python.

a = [array([1. , 1.2, 1.4, 1.6, 1.8]),
     array([0. , 0.2, 0.4, 0.6, 0.8]),
     array([0. , 0.2, 0.4, 0.6])]

Кто-нибудь может мне это объяснить?

Ответы [ 6 ]

0 голосов
/ 19 февраля 2019

@ coldspeed правильно объяснил, почему вы видите разницу в поведении.Я просто хотел указать, что копирование ожидается.

В документации вы можете видеть, что функция имеет флаг копирования, установленный по умолчанию True:

numpy.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)

Если копия должна быть толькосделайте, если необходимо, вместо этого используйте np.asarray.

В вашем примере это не имеет большого значения, потому что a - это список, а не просто массив, поэтому он всегда будет скопирован.

Если бы a был массивом, поведение было бы следующим:

import numpy as np
a = np.array([[0.0, 0.2, 0.4, 0.6, 0.8],
              [0.0, 0.2, 0.4, 0.6, 0.8],
              [0.0, 0.2, 0.4, 0.6, 0.8]])
b=np.array(a)
b[0] += 1
a

Out[6]: 
array([[0. , 0.2, 0.4, 0.6, 0.8],
       [0. , 0.2, 0.4, 0.6, 0.8],
       [0. , 0.2, 0.4, 0.6, 0.8]])
c = np.asarray(a)
c[0] +=1
a

Out[9]: 
array([[1. , 1.2, 1.4, 1.6, 1.8],
       [0. , 0.2, 0.4, 0.6, 0.8],
       [0. , 0.2, 0.4, 0.6, 0.8]])
0 голосов
/ 19 февраля 2019

Основным вариантом использования, для которого было разработано numpy.array(), является создание n-мерного массива чисел, в котором все числа хранятся в собственной эффективно спроектированной внутренней структуре numpy.

Всякий раз, когда это происходит возможно , чтобы сделать это, numpy.array() действительно сделает это.

(Эффективность этой внутренней структуры была бы вашей основной причиной для использования numy ndarrays, а не списков Python, поэтому фактто, что числа копируются, должно быть для вас желательным / хорошим делом)

Когда ваш a представляет собой список из 3 ndarray, каждый размером 5, он явно возможен для numpy.array() для создания n-мерного ndarray чисел (в частности, 2-мерного, с формой (3,5)).

Таким образом, любое изменение b[0] фактически является изменением этих внутренних данных.структура чисел, которые были скопированы из a.

Когда ваш a является списком ndarrays неравного размера, больше невозможно для numpy.array()преобразовать это в n-мерный массив формы (3,5).

Итак, функция делает следующую лучшую вещь, которую она может сделать, то есть обрабатывать каждый из 3-х массивов как object, ивернуть 1-мерный ndarray из этих object s.Длина этого возвращаемого ndarray составляет 3 (число object s).Это можно увидеть, напечатав b.shape (напечатает (3,) вместо (3,5)) и b.dtype (напечатает object вместо float64).

В этом случае numpy.array() не погружается глубже в каждый из ваших 3 ndarrays, чтобы скопировать числа этих 3 ndarrays, так как он не собирается создавать свой собственный эффективно разработанный n-мерный массив чисел - он только собираетсявернуть одномерный массив object с.

Таким образом, любое изменение, которое вы вносите в b[0], также можно увидеть через a, поскольку как a, так и b содержат ссылки нате же object с (3 луча неравных размеров).

0 голосов
/ 19 февраля 2019

Когда вы создаете np.array с последовательными длинами списков, создается новый объект np.ndarray из float s.

Таким образом, ваши a[0] и b[0] не имеют одинаковых ссылок.

a = [np.array([0.0, 0.2, 0.4, 0.6, 0.8]),
     np.array([0.0, 0.2, 0.4, 0.6, 0.8]),
     np.array([0.0, 0.2, 0.4, 0.6, 0.8])]
b = np.array(a)
id(a[0])
# 139663994327728
id(b[0])
# 139663994324672

Однако, с различными длинами списков, np.array создает np.ndarray с object в качестве его элементов.

a2 = [np.array([0. , 0.2, 0.4, 0.6, 0.8]), 
     np.array([0. , 0.2, 0.4, 0.6, 0.8]), 
     np.array([0. , 0.2, 0.4, 0.6])]
b2 = np.array(a2)
b2
array([array([1. , 1.2, 1.4, 1.6, 1.8]), array([0. , 0.2, 0.4, 0.6, 0.8]),
       array([0. , 0.2, 0.4, 0.6])], dtype=object)

Где b2 все еще остается прежнимссылки из a2:

for s in a2:
    print(id(s))
# 139663994330128
# 139663994328448
# 139663994329488

for s in b2:
    print(id(s))
# 139663994330128
# 139663994328448
# 139663994329488

, что добавляет к b2[0] результаты в дополнение к a2[0].

0 голосов
/ 19 февраля 2019
In [1]: a = [np.array([0.0, 0.2, 0.4, 0.6, 0.8]), 
   ...:      np.array([0.0, 0.2, 0.4, 0.6, 0.8]), 
   ...:      np.array([0.0, 0.2, 0.4, 0.6, 0.8])]                               
In [2]:                                                                         
In [2]: a                                                                       
Out[2]: 
[array([0. , 0.2, 0.4, 0.6, 0.8]),
 array([0. , 0.2, 0.4, 0.6, 0.8]),
 array([0. , 0.2, 0.4, 0.6, 0.8])]

a - список массивов.b - это двумерный массив.

In [3]: b = np.array(a)                                                         
In [4]: b                                                                       
Out[4]: 
array([[0. , 0.2, 0.4, 0.6, 0.8],
       [0. , 0.2, 0.4, 0.6, 0.8],
       [0. , 0.2, 0.4, 0.6, 0.8]])
In [5]: b[0] += 1                                                               
In [6]: b                                                                       
Out[6]: 
array([[1. , 1.2, 1.4, 1.6, 1.8],
       [0. , 0.2, 0.4, 0.6, 0.8],
       [0. , 0.2, 0.4, 0.6, 0.8]])

b получает значения из a, но не содержит ни одного из объектов a.Базовая структура данных этого b очень отличается от a, списка.Если это неясно, вы можете рассмотреть основы numpy (которые говорят о форме, шагах и буферах данных).

Во втором случае b - это массив объектов, содержащийте же объекты, что и a:

In [8]: b = np.array(a)                                                         
In [9]: b                                                                       
Out[9]: 
array([array([0. , 0.2, 0.4, 0.6, 0.8]), array([0. , 0.2, 0.4, 0.6, 0.8]),
       array([0. , 0.2, 0.4, 0.6])], dtype=object)

Этот b ведет себя так же, как a - оба содержат массивы.

Конструкция этого массива объектов сильно отличается от2-й числовой массив.Я думаю о числовом массиве как о стандартном, или обычном, недобросовестном поведении, в то время как массив объектов - это «уступка», дающая нам полезный инструмент, но не имеющий вычислительных мощностей многомерного массива.

Создать объектный массив легко по ошибке - некоторые говорят, что это слишком просто.Это может быть сложнее сделать надежно по замыслу.Для примера с оригинальным a нам нужно сделать:

In [17]: b = np.empty(3, object)                                                
In [18]: b[:] = a[:]                                                            
In [19]: b                                                                      
Out[19]: 
array([array([0. , 0.2, 0.4, 0.6, 0.8]), array([0. , 0.2, 0.4, 0.6, 0.8]),
       array([0. , 0.2, 0.4, 0.6, 0.8])], dtype=object)

или даже for i in range(3): b[i] = a[i]

0 голосов
/ 19 февраля 2019

В двух словах, это следствие ваших данных.Вы заметите, что это работает / не работает (в зависимости от того, как вы его просматриваете), потому что ваши массивы не одинакового размера .

С подобраками подобного размера элементы могут быть компактно загружены в эффективную схему памяти, где любой массив ND может быть представлен компактным 1-D массивом в памяти.Затем NumPy самостоятельно обрабатывает перевод многомерных индексов в одномерные индексы.Например, индекс [i, j] двумерного массива будет отображаться в i * N + j (если хранится в основном формате строки).Данные из исходного списка массивов копируются в компактный одномерный массив, поэтому любые изменения, внесенные в этот массив, не влияют на исходный.

При использовании рваных списков / массивов это сделать невозможно.Массив фактически является списком Python, где каждый элемент является объектом Python.Для эффективности копируются только ссылки на объекты, а не данные.Вот почему вы можете изменить исходные элементы списка во втором случае, но не в первом.

0 голосов
/ 19 февраля 2019

В первом случае NumPy видит, что входные данные для numpy.array могут быть интерпретированы как 3x5, как двумерный массив, поэтому он делает это.В результате получается новый массив типа float64, в который копируются входные данные, независимо от входного объекта.b[0] - это вид первой строки нового массива, полностью независимой от a[0], и изменение b[0] не влияет на a[0].

Во втором случае, поскольку длины подмассивовНеравный вход не может быть интерпретирован как двумерный массив.Тем не менее, рассматривая подмассивы как непрозрачные объекты, список можно интерпретировать как одномерные массивы, подобные объектам, к которым прибегает NumPy.Результатом вызова numpy.array является одномерный массив объекта dtype, содержащий ссылки на объекты массива, которые были элементами входного списка.b[0] является тем же объектом массива, что и a[0], и b[0] += 1 мутирует этот объект.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...