Состояние устаревания класса матрицы NumPy - PullRequest
0 голосов
/ 12 ноября 2018

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

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

1 Ответ

0 голосов
/ 12 ноября 2018

ТЛ; dr: класс numpy.matrix устарел. Существуют некоторые высококлассные библиотеки, которые зависят от класса в качестве зависимости (самая большая из которых 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,:] для двумерного массива дают вам 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). Два вещают вместе, чтобы сформировать (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) не могут быть умножены в этом порядке.

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

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

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

Уже в ветхой рассылке от 2009 Я нашел такие замечания, как

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

( источник ) и

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

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

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

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

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

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

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

( источник ).

Статус-кво

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

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

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

И, наконец, в «дорожной карте» NumPy по состоянию на ноябрь 2018 г. упоминается множество связанных тем в качестве одной из « задач и возможностей [сообщество NUMPY] будет инвестировать ресурсы в »:

Некоторые вещи внутри NumPy на самом деле не соответствуют сфере действия NumPy.

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

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

...