Разница между формой numpy.array(R, 1) и (R,)

В numpy некоторые из операций возвращаются в форме (R, 1), но некоторые возвращают (R,). Это сделает умножение матрицы более утомительным, так как требуется явный reshape. Например, учитывая матрицу M, если мы хотим сделать numpy.dot(M[:,0], numpy.ones((1, R))), где R - количество строк (конечно, одна и та же проблема также возникает по столбцам). Мы получим ошибку matrices are not aligned, так как M[:,0] имеет форму (R,), но numpy.ones((1, R)) имеет форму (1, R).

Итак, мои вопросы:

  • Какая разница между формой (R, 1) и (R,). Я знаю буквально это список номеров и список списков, где весь список содержит только число. Просто задайтесь вопросом, почему бы не сконструировать numpy так, чтобы он облегчал форму (R, 1) вместо (R,) для упрощения матричного умножения.

  • Есть ли лучшие способы для приведенного выше примера? Без явной перестройки: numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

Ответ 1

1. Значение фигур в NumPy

Вы пишете: "Я знаю буквально это список номеров и список списков, где весь список содержит только число", но это немного бесполезный способ подумать об этом.

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

Например, если мы создадим массив из 12 целых чисел:

>>> a = numpy.arange(12)
>>> a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

Затем a состоит из буфера данных, упорядоченного примерно так:

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

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

>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)

Здесь форма (12,) означает, что массив индексируется одним индексом, который исходит от 0 до 11. Концептуально, если мы помечаем этот единственный индекс i, массив a выглядит следующим образом:

i= 0    1    2    3    4    5    6    7    8    9   10   11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

Если мы reshape массив, это не изменит буфер данных. Вместо этого он создает новое представление, которое описывает другой способ интерпретации данных. Итак, после:

>>> b = a.reshape((3, 4))

массив b имеет тот же буфер данных, что и a, но теперь он индексируется двумя индексами, которые работают от 0 до 2 и от 0 до 3 соответственно. Если мы помечаем два индекса i и j, массив b выглядит следующим образом:

i= 0    0    0    0    1    1    1    1    2    2    2    2
j= 0    1    2    3    0    1    2    3    0    1    2    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

что означает, что:

>>> b[2,1]
9

Вы можете видеть, что второй индекс изменяется быстро, и первый индекс медленно изменяется. Если вы предпочитаете, чтобы это было наоборот, вы можете указать параметр order:

>>> c = a.reshape((3, 4), order='F')

который приводит к индексированию массива следующим образом:

i= 0    1    2    0    1    2    0    1    2    0    1    2
j= 0    0    0    1    1    1    2    2    2    3    3    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

что означает, что:

>>> c[2,1]
5

Теперь должно быть ясно, что означает, что массив имеет форму с одним или несколькими размерами размера 1. После:

>>> d = a.reshape((12, 1))

массив d индексируется двумя индексами, первый из которых выполняется от 0 до 11, а второй индекс всегда равен 0:

i= 0    1    2    3    4    5    6    7    8    9   10   11
j= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

и так:

>>> d[10,0]
10

Измерение длины 1 является "бесплатным" (в некотором смысле), поэтому вам ничего не мешает отправиться в город:

>>> e = a.reshape((1, 2, 1, 6, 1))

дает массив, индексированный следующим образом:

i= 0    0    0    0    0    0    0    0    0    0    0    0
j= 0    0    0    0    0    0    1    1    1    1    1    1
k= 0    0    0    0    0    0    0    0    0    0    0    0
l= 0    1    2    3    4    5    0    1    2    3    4    5
m= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

и так:

>>> e[0,1,0,0,0]
6

Подробнее о том, как массивы будут реализованы, см. документацию по внутренним документам NumPy.

2. Что делать?

Так как numpy.reshape просто создает новое представление, вы не должны бояться использовать его, когда это необходимо. Это правильный инструмент для использования, когда вы хотите индексировать массив по-другому.

Однако при длительном вычислении обычно можно организовать сборку массивов с "правой" формой в первую очередь и таким образом минимизировать количество преобразований и транспонирует. Но, не видя фактического контекста, который привел к необходимости изменения, трудно сказать, что следует изменить.

Пример в вашем вопросе:

numpy.dot(M[:,0], numpy.ones((1, R)))

но это нереально. Во-первых, это выражение:

M[:,0].sum()

более просто вычисляет результат. Во-вторых, действительно ли что-то особенное в колонке 0? Возможно, вам действительно нужно:

M.sum(axis=0)

Ответ 2

Разница между (R,) и (1,R) - это буквально количество индексов, которые вам нужно использовать. ones((1,R)) - это 2-мерный массив, который имеет только одну строку. ones(R) - вектор. Обычно, если переменная не имеет смысла иметь более одной строки/столбца, вы должны использовать вектор, а не матрицу с одномерным размером.

В вашем конкретном случае есть несколько вариантов:

1) Просто сделайте второй аргумент вектором. Следующее прекрасно работает:

    np.dot(M[:,0], np.ones(R))

2) Если вам нужны операции с матричной матрицей, используйте класс matrix вместо ndarray. Все матрицы вынуждены представлять собой двумерные массивы, а оператор * выполняет умножение матрицы вместо элементарного (так что вам не нужна точка). По моему опыту, это больше проблем, потому что это стоит того, но может быть приятно, если вы привыкли к matlab.

Ответ 3

Для своего базового класса массивов 2d массивы не более чем 1d или 3-мерные. Есть некоторые операции, которые сохраняют размеры, некоторые, которые их уменьшают, другие объединяют или даже расширяют их.

M=np.arange(9).reshape(3,3)
M[:,0].shape # (3,) selects one column, returns a 1d array
M[0,:].shape # same, one row, 1d array
M[:,[0]].shape # (3,1), index with a list (or array), returns 2d
M[:,[0,1]].shape # (3,2)

In [20]: np.dot(M[:,0].reshape(3,1),np.ones((1,3)))

Out[20]: 
array([[ 0.,  0.,  0.],
       [ 3.,  3.,  3.],
       [ 6.,  6.,  6.]])

In [21]: np.dot(M[:,[0]],np.ones((1,3)))
Out[21]: 
array([[ 0.,  0.,  0.],
       [ 3.,  3.,  3.],
       [ 6.,  6.,  6.]])

Другие выражения, которые дают один и тот же массив

np.dot(M[:,0][:,np.newaxis],np.ones((1,3)))
np.dot(np.atleast_2d(M[:,0]).T,np.ones((1,3)))
np.einsum('i,j',M[:,0],np.ones((3)))
M1=M[:,0]; R=np.ones((3)); np.dot(M1[:,None], R[None,:])

MATLAB начинался с двухмерных массивов. Более новые версии допускают большее количество измерений, но сохраняют нижнюю границу 2. Но вам все равно нужно обратить внимание на разницу между матрицей строк и столбцом один с формой (1,3) v (3,1). Как часто вы пишете [1,2,3].'? Я собирался написать row vector и column vector, но с этим ограничением 2d в MATLAB нет векторов - по крайней мере, в математическом смысле вектор не равен 1d.

Вы просмотрели np.atleast_2d (также версии _1d и _3d)?

Ответ 4

Форма представляет собой кортеж. Если есть только 1 размер, форма будет одним номером и будет просто пустой после запятой. Для 2+ измерений после запятых будет число.

# 1 dimension with 2 elements, shape = (2,). 
# Note there nothing after the comma.
z=np.array([  # start dimension
    10,       # not a dimension
    20        # not a dimension
])            # end dimension
print(z.shape)

(2,)

# 2 dimensions, each with 1 element, shape = (2,1)
w=np.array([  # start outer dimension 
    [10],     # element is in an inner dimension
    [20]      # element is in an inner dimension
])            # end outer dimension
print(w.shape)

(2,1)

Ответ 5

1) Причина не в выборе формы (R, 1) над (R,) заключается в том, что она излишне усложняет ситуацию. Кроме того, почему было бы предпочтительнее иметь форму (R, 1) по умолчанию для вектора длины-R вместо (1, R)? Лучше держать его простым и быть явным, когда вам нужны дополнительные размеры.

2) В вашем примере вы вычисляете внешний продукт, чтобы сделать это без вызова reshape с помощью np.outer:

np.outer(M[:,0], numpy.ones((1, R)))