tl; dr: класс numpy.matrix
устарел. Есть несколько высокопрофильных библиотек, которые зависят от класса как от зависимости (самая большая из них - scipy.sparse
), что препятствует надлежащему краткосрочному устареванию класса, но пользователям настоятельно рекомендуется использовать класс ndarray
(обычно создаваемый с использованием _ 4_ вспомогательная функция). С введением оператора @
для умножения матриц многие относительные преимущества матриц были устранены.
Почему (не) матричный класс?
numpy.matrix
является подклассом numpy.ndarray
. Первоначально он был предназначен для удобного использования в вычислениях с использованием линейной алгебры, но есть как ограничения, так и удивительные различия в их поведении по сравнению с экземплярами более общего класса массивов. Примеры фундаментальных различий в поведении:
- Формы: массивы могут иметь произвольное количество измерений от 0 до бесконечности (или 32). Матрицы всегда двумерны. Как ни странно, хотя матрицу нельзя создать с большим количеством измерений, можно ввести одноэлементные измерения в матрицу, чтобы технически получить многомерную матрицу:
np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1)
(не то чтобы это имеет какое-либо практическое значение ).
- Индексирование: индексирующие массивы могут давать вам массивы любого размера в зависимости от того, как вы их индексируете < / а>. Индексирование выражений в матрицах всегда дает матрицу. Это означает, что и
arr[:,0]
, и arr[0,:]
для 2-мерного массива дают вам 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
< / a>, который использует обе семантики numpy.matrix
и часто возвращает матрицы при уплотнении), поэтому полностью отказаться от них всегда было проблематично.
Уже в нумерации рассылки за 2009 год я обнаружил такие замечания, как
numpy был разработан для общих вычислительных нужд, а не для какой-либо одной области математики. nd-массивы очень полезны для многих вещей. Напротив, Matlab, например, изначально разрабатывался как простой интерфейс для пакета линейной алгебры. Лично, когда я использовал Matlab, я обнаружил, что это очень неудобно - я обычно писал сотни строк кода, которые не имели ничего общего с линейной алгеброй, на каждые несколько строк, которые действительно выполняли матричную математику. Так что я предпочитаю numpy - строки кода линейной алгебры длиннее и неудобнее, но остальное намного лучше.
Исключением является класс Matrix: он был написан, чтобы обеспечить естественный способ выражения линейной алгебры. Однако, когда вы смешиваете матрицы и массивы, все становится немного сложнее, и даже при использовании матриц возникают путаницы и ограничения - как вы выразите строку по сравнению с вектором-столбцом? что вы получаете, когда перебираете матрицу? и т.п.
Было много дискуссий по этим вопросам, было много хороших идей, немного консенсуса по поводу того, как это улучшить, но ни у кого, обладающего навыками для этого, нет достаточной мотивации для этого.
Они отражают преимущества и трудности, связанные с матричным классом. Самое раннее предложение об устаревании, которое я смог найти, - это от 2008 года , хотя частично мотивировано неинтуитивным поведением, которое изменилось с тех пор (в частности, нарезка и итерация по матрице приведут к (строковым) матрицам, как и следовало ожидать). Предложение показало, что это очень спорная тема и что инфиксные операторы для матричного умножения имеют решающее значение.
Следующее упоминание, которое я смог найти, относится к 2014 году, которое оказалось чтобы стать очень плодотворной темой. Последующее обсуждение поднимает вопрос об обработке numpy подклассов в целом, какая общая тема все еще обсуждается. Также существует резкая критика:
Это обсуждение (на 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.
Поэтому я думаю, что путь вперед будет примерно таким:
Сейчас или всякий раз, когда кто-то собирает PR: выдайте PendingDeprecationWarning в np.matrix .__ init__ (если это не убивает производительность для scikit-learn и друзей) и поместите большое окно с предупреждением в верхней части документации. Идея здесь состоит в том, чтобы на самом деле не нарушать чей-либо код, а начать выводить сообщение о том, что мы определенно не думаем, что кто-то должен использовать это, если у них есть какая-либо альтернатива.
После того, как есть альтернатива scipy.sparse: увеличьте количество предупреждений, возможно, вплоть до FutureWarning, чтобы существующие скрипты не ломались, но получали шумные предупреждения
В конце концов, если мы думаем, что это снизит затраты на обслуживание: разделите его на подпакет
(источник).
Статус кво
По состоянию на май 2018 г. (numpy 1.15, соответствующий запрос на вытягивание и commit) строка документации класса матрицы содержит следующее примечание:
Больше не рекомендуется использовать этот класс даже для линейной алгебры. Вместо этого используйте обычные массивы. В будущем этот класс может быть удален.
И в то же время PendingDeprecationWarning
был добавлен к matrix.__new__
. К сожалению, предупреждения об устаревании (почти всегда) по умолчанию отключены, поэтому большинство конечных пользователей numpy не увидят этого сильного намека.
Наконец, в дорожной карте numpy по состоянию на ноябрь 2018 г. упоминается несколько связанных тем. в качестве одной из «задач и функций [сообщество numpy] будет инвестировать ресурсы»:
Некоторые вещи внутри NumPy на самом деле не соответствуют области действия NumPy.
- Бэкэнд-система для numpy.fft (так что, например, fft-mkl не нужно использовать monkeypatch numpy)
- Переписать маскированные массивы, чтобы они не были подклассом ndarray - может быть, в отдельном проекте?
- MaskedArray как тип утиного массива и / или
- dtypes, которые поддерживают пропущенные значения
- Напишите стратегию того, как бороться с перекрытием между numpy и scipy для linalg и fft (и реализовать ее).
- Устарело np.matrix
Вполне вероятно, что это состояние будет оставаться до тех пор, пока более крупные библиотеки / многие пользователи (и в частности scipy.sparse
) будут полагаться на матричный класс. Однако существует постоянное обсуждение перехода scipy.sparse
в зависимость от чего-то еще, например _ 67_. Независимо от развития процесса устаревания, пользователи должны использовать класс ndarray
в новом коде и, по возможности, переносить более старый код. В конечном итоге матричный класс, вероятно, окажется в отдельном пакете, чтобы снять часть проблем, связанных с его существованием в его нынешней форме.
person
Andras Deak
schedule
12.11.2018