документация - python numpy api documentation



Статус устаревания матричного класса NumPy (1)

Каков статус matrix класса в NumPy?

Мне все время говорят, что я должен использовать класс ndarray . Стоит ли / безопасно использовать класс matrix в новом коде, который я пишу? Я не понимаю, почему я должен использовать ndarray .


ТЛ; dr: класс numpy.matrix становится устаревшим. Существует несколько высокопрофильных библиотек, которые зависят от класса как зависимости (самый большой из них - scipy.sparse ), что препятствует надлежащему краткосрочному scipy.sparse класса, но пользователям настоятельно рекомендуется использовать класс ndarray (обычно созданный с использованием numpy.array функция удобства). С введением оператора @ для матричного умножения было удалено множество относительных преимуществ матриц.

Почему (нет) матричный класс?

numpy.matrix - это подкласс numpy.ndarray . Первоначально это предназначалось для удобного использования в вычислениях с использованием линейной алгебры, но есть и ограничения, и удивительные различия в том, как они ведут себя по сравнению с экземплярами более общего класса массива. Примеры фундаментальных различий в поведении:

  • Формы: массивы могут иметь произвольное число измерений от 0 до бесконечности (или 32). Матрицы всегда двумерны. Как ни странно, в то время как матрица не может быть создана с большими размерами, можно ввести размеры синглтона в матрицу, чтобы в итоге получить технически многомерную матрицу: np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1) (не так, чтобы это имело практическое значение).
  • Индексирование: массивы индексирования могут давать вам массивы любого размера в зависимости от того, как вы их индексируете . Индексирование выражений на матрицах всегда даст вам матрицу. Это означает, что оба arr[:,0] и arr[0,:] для 2d-массива дают вам 1d ndarray , а mat[:,0] имеет форму (N,1) и mat[0,:] имеет форму (1,M) в случае matrix .
  • Арифметические операции: основной причиной использования матриц в прежние времена было то, что арифметические операции (в частности, умножение и мощность) на матрицах выполняют матричные операции (матричное умножение и мощность матрицы). То же самое для массивов приводит к элементарному умножению и мощности. Следовательно, mat1 * mat2 действителен, если mat1.shape[1] == mat2.shape[0] , но arr1 * arr2 действителен, если arr1.shape == arr2.shape (и, конечно, результат означает нечто совершенно другое). Также удивительно, что mat1 / mat2 выполняет элементное разделение двух матриц. Такое поведение, вероятно, унаследовано от ndarray но не имеет смысла для матриц, особенно в свете значения * .
  • Специальные атрибуты: у матриц есть несколько удобных атрибутов в дополнение к тому, что у массивов: mat.A и mat.A1 - это виды массивов с тем же значением, что и np.array(mat) и np.array(mat).ravel() , соответственно , mat.T и mat.H - транспонированная и сопряженная транспозиция (сопряженная) матрицы; arr.T - единственный такой атрибут, который существует для класса ndarray . Наконец, mat.I - обратная матрица mat .

Это достаточно легко писать код, который работает либо для ndarrays, либо для матриц. Но когда есть шанс, что два класса должны взаимодействовать в коде, все начинает становиться трудным. В частности, много кода может работать естественно для подклассов ndarray , но matrix - это подкласс, который может легко сломать код, который пытается полагаться на утиную типизацию. Рассмотрим следующий пример с использованием массивов и матриц формы (3,4) :

import numpy as np

shape = (3, 4)
arr = np.arange(np.prod(shape)).reshape(shape) # ndarray
mat = np.matrix(arr) # same data in a matrix
print((arr + mat).shape)           # (3, 4), makes sense
print((arr[0,:] + mat[0,:]).shape) # (1, 4), makes sense
print((arr[:,0] + mat[:,0]).shape) # (3, 3), surprising

Добавление срезов двух объектов катастрофически различается в зависимости от размера, по которому мы срезаем. Добавление на обеих матрицах и массивах происходит элементарно, когда фигуры одинаковы. Первые два случая в приведенном выше примере являются интуитивными: мы добавляем два массива (матрицы), затем добавляем по две строки из каждого. Последний случай действительно удивителен: мы, вероятно, хотели добавить два столбца и в итоге получили матрицу. Разумеется, причина в том, что arr[:,0] имеет форму (3,) которая совместима с формой (1,3) , но mat[:.0] имеет форму (3,1) . Эти два broadcast вместе для формирования (3,3) .

Наконец, наибольшее преимущество матричного класса (т. Е. Возможность краткой формулировки сложных матричных выражений с участием множества матричных продуктов) было удалено, когда оператор @ matmul был введен в python 3.5 , сначала реализованный в numpy 1.10 . Сравните вычисление простой квадратичной формы:

v = np.random.rand(3); v_row = np.matrix(v)
arr = np.random.rand(3,3); mat = np.matrix(arr)

print(v.dot(arr.dot(v))) # pre-matmul style
# 0.713447037658556, yours will vary
print(v_row * mat * v_row.T) # pre-matmul matrix style
# [[0.71344704]]
print(v @ arr @ v) # matmul style
# 0.713447037658556

Рассматривая вышеизложенное, понятно, почему матричный класс был широко предпочтен для работы с линейной алгеброй: оператор infix * сделал выражения гораздо менее подробными и гораздо более легкими для чтения. Однако мы получаем такую ​​же читаемость с помощью оператора @ используя современный python и numpy. Кроме того, заметим, что матричный случай дает нам матрицу формы (1,1) которая технически должна быть скаляром. Это также означает, что мы не можем умножить вектор-столбец с этим «скаляром»: (v_row * mat * v_row.T) * v_row.T в приведенном выше примере вызывает ошибку, потому что матрицы с формой (1,1) и (3,1) не может быть умножен в этом порядке.

Для полноты следует отметить, что хотя матричный оператор фиксирует наиболее распространенный сценарий, в котором ndarrays являются субоптимальными по сравнению с матрицами, все же есть несколько недостатков в правильном использовании линейной алгебры с использованием ndarrays (хотя люди все еще склонны полагать, что в целом это предпочтительнее придерживаться последних). Одним из таких примеров является матричная мощность: mat ** 3 - правильная третья матричная мощность матрицы (тогда как это элементный куб ndarray). К сожалению, numpy.linalg.matrix_power довольно многословна. Кроме того, умножение матрицы на месте работает отлично только для класса матрицы. Напротив, хотя оба PEP 465 и грамматика python позволяют @= как расширенное задание с matmul, это не реализовано для ndarrays с numpy 1.15.

История устаревания

С учетом вышеперечисленных осложнений, связанных с классом matrix в течение длительного времени неоднократно обсуждались его возможные отклонения. Внедрение оператора @ infix, который был огромным предварительным условием для этого процесса, произошел в сентябре 2015 года . К сожалению, преимущества матричного класса в предыдущие дни означали, что его использование широко распространено. Существуют библиотеки, которые зависят от класса матрицы (одним из наиболее важных зависимых является scipy.sparse который использует семантику numpy.matrix и часто возвращает матрицы при уплотнении), поэтому их полное обесценивание всегда было проблематичным.

Уже в нескольких тысячах списков рассылки от 2009 года я нашел замечания, такие как

numpy был разработан для вычислительных потребностей общего назначения, а не для одной ветви математики. nd-массивы очень полезны для многих вещей. Напротив, Matlab, например, был первоначально разработан как простой интерфейс для линейной алгебры. Лично, когда я использовал Matlab, я обнаружил, что это очень неудобно - я обычно писал 100 строк строк, которые не имели никакого отношения к линейной алгебре, для каждых нескольких строк, которые на самом деле выполняли математическую математику. Поэтому я очень предпочитаю метод numpy - линейные линии алгебры кода более неловкие, но все остальное намного лучше.

Класс Matrix является исключением: он написан для естественного способа выражения линейной алгебры. Однако, когда вы смешиваете матрицы и массивы, все становится немного сложнее, и даже когда вы придерживаетесь матриц, есть путаницы и ограничения - как вы выражаете строку против вектора столбца? что вы получаете, когда вы перебираете матрицу? и т.п.

Там была куча дискуссии по этим вопросам, много хороших идей, немного консенсуса о том, как ее улучшить, но никто из обладающих навыками сделать это не имеет достаточной мотивации для этого.

Они отражают преимущества и трудности, связанные с классом матриц. Самое раннее предложение об устаревании, которое я смог найти, - с 2008 года , хотя и частично мотивированное неинтуитивным поведением, которое изменилось с тех пор (в частности, нарезка и итерация по матрице приведут к (строковым) матрицам, как можно было бы ожидать). Предложение показало, что это очень спорный вопрос и что операторы infix для матричного умножения имеют решающее значение.

Следующее упоминание, которое я могу найти, это с 2014 года, которое оказалось очень плодотворной нитью. Последующая дискуссия поднимает вопрос об обработке подклассов numpy в целом, общая тема которых по-прежнему очень важна для таблицы . Существует также сильная критика :

Что вызвало эту дискуссию (на Github), так это то, что писать код утиного типа, который работает правильно, невозможно:

  • ndarrays
  • матрицы
  • scipy.sparse разреженные матрицы

Семантика всех трех разных; scipy.sparse находится где-то между матрицами и ndarrays, некоторые вещи работают случайным образом, как матрицы, а другие нет.

С добавлением некоторой hyberbole можно сказать, что с точки зрения разработчика np.matrix делает и уже делала зло только существующим путем испорчения неустановленных правил семантики ndarray в Python.

за которым следует много ценных обсуждений возможных фьючерсов для матриц. Даже при отсутствии оператора @ в то время было много мысли, придаваемых устаревшему классу матриц, и как это может повлиять на пользователей вниз по течению. Насколько я могу судить, это обсуждение непосредственно привело к созданию PEP 465, вводящего matmul.

В начале 2015 года :

На мой взгляд, «фиксированная» версия np.matrix должна (1) не быть подклассом np.ndarray и (2) существовать в сторонней библиотеке, а не в numpy.

Я не думаю, что вполне реально исправить np.matrix в текущем состоянии как подкласс ndarray, но даже фиксированный матричный класс действительно не принадлежит самому numpy, который имеет слишком длинные циклы выпуска и гарантии совместимости для экспериментов - не говоря уже о том, что простое существование матричного класса в numpy приводит к сбиванию новых пользователей.

Как только оператор @ был доступен некоторое время, обсуждение устаревания всплыло снова , повторив тему о взаимосвязи матричной scipy.sparse и scipy.sparse .

В конце концов, первое действие для numpy.matrix было принято в конце ноября 2017 года . Что касается иждивенцев класса:

Как сообщество будет обрабатывать подклассы матрицы scipy.sparse? Они все еще широко используются.

Они не собираются никуда долгое время (пока редкие ndarrays не материализуются как минимум). Следовательно, np.matrix нужно перемещать, а не удалять.

( source ) и

в то время как я хочу избавиться от np.matrix так же, как и любой, делая это в ближайшее время, будет действительно разрушительным.

  • Есть тонны небольших сценариев, написанных людьми, которые не знали лучше; мы хотим, чтобы они научились не использовать np.matrix, но нарушение всех их скриптов - это болезненный способ сделать это

  • Существуют крупные проекты, такие как scikit-learn, которые просто не имеют альтернативы использованию np.matrix из-за scipy.sparse.

Поэтому я думаю, что путь вперед - это что-то вроде:

  • Теперь или всякий раз, когда кто-то собирает PR: выдает PendingDeprecationWarning в np.matrix .__ init__ (если только он не убивает производительность для scikit-learn и друзей) и помещает большое предупреждение в верхнюю часть документов. Идея здесь состоит в том, чтобы на самом деле не сломать чей-то код, но начать выходить из сообщения, что мы определенно не считаем, что кто-то должен использовать это, если у них есть альтернатива.

  • После того, как есть альтернатива scipy.sparse: увеличьте предупреждения, возможно, вплоть до FutureWarning, чтобы существующие скрипты не сломались, но у них есть шумные предупреждения

  • В конце концов, если мы подумаем, что это снизит затраты на обслуживание: разделите его на подпакет

( source ).

Статус кво

По состоянию на май 2018 года (numpy 1.15, соответствующий запрос на перенос и commit ) матричная класс docstring содержит следующее примечание:

Больше не рекомендуется использовать этот класс даже для линейной алгебры. Вместо этого используйте регулярные массивы. Класс может быть удален в будущем.

И в то же время в PendingDeprecationWarning добавлена matrix.__new__ . К сожалению, предупреждения об устаревании (почти всегда) отключены по умолчанию , поэтому большинство конечных пользователей numpy не будут видеть этот сильный намек.

Наконец, в плане номенклатуры numpy по состоянию на ноябрь 2018 года упоминается несколько связанных тем, поскольку одна из « задач и функций [сообщество numpy] будет инвестировать ресурсы в :

Некоторые вещи внутри NumPy фактически не соответствуют масштабу NumPy.

  • Бэкэнд-система для numpy.fft (так что, например, fft-mkl не требуется numkeypatch numpy)
  • Переписать маскированные массивы, чтобы не быть подклассом ndarray - возможно, в отдельном проекте?
  • MaskedArray как тип утиного массива и / или
  • dtypes, которые поддерживают отсутствующие значения
  • Напишите стратегию о том, как справиться с перекрытием между numpy и scipy для linalg и fft (и реализовать его).
  • Заблокировать np.matrix

Вероятно, это состояние останется таким, как если бы более крупные библиотеки / многие пользователи (и, в частности, scipy.sparse ) полагались на класс матрицы. Тем не менее, продолжается дискуссия, чтобы переместить scipy.sparse чтобы зависеть от чего-то другого, такого как pydata/sparse . Независимо от развития процесса устаревания пользователи должны использовать класс ndarray в новом коде и, по возможности, предпочтительно использовать более старый код порта. В конце концов, матричный класс, вероятно, окажется в отдельном пакете, чтобы удалить часть бремени, вызванную его существованием в его текущей форме.





deprecated