ТЛ; 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
в новом коде и предпочтительно портировать старый код, если это возможно. В конце концов, матричный класс, вероятно, окажется в отдельном пакете, чтобы снять некоторые нагрузки, вызванные его существованием в его текущей форме.