Статус устаревания класса матрицы NumPy

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

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


person Andras Deak    schedule 12.11.2018    source источник


Ответы (1)


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

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

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

Достаточно просто написать код, который работает как с 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
comment
Я не считаю scipy.sparse зависимым от np.matrix. Да, так как реализовано только в 2d, и использование операторов является моделью для версии np. Но ни один из разреженных форматов не является подклассом np.matrix. А преобразователь в np.matrix, sparse.todense фактически реализован как np.asmatrix(M.toarray()). - person hpaulj; 12.11.2018
comment
Первоначально sparse был создан для линейной алгебры, при этом csr и csc были центральными, а другие форматы служили инструментами создания. Он был смоделирован на основе кода MATLAB, который, насколько я могу судить, ограничен форматом csc. Однако sparse все больше используется в машинном обучении и использовании больших данных. sklearn имеет набор собственных редких утилит. Я не знаю, выигрывают ли другие виды использования от nd разреженных массивов или нет. Возможно, косвенно pandas имеет свои собственные версии разреженности (серии и фреймы данных). - person hpaulj; 12.11.2018
comment
@hpaulj, насколько я понимаю, проблема вне зависимости. В приведенных выше цепочках писем упоминалось, что семантика scipy.sparse также близко имитирует семантику matrix (я попытаюсь найти соответствующие сообщения). А также примеры, когда неявные операции, приводящие к плотному результату, создавали множество матриц; хотя, возможно, это уже не так, учитывая возраст некоторых цепочек писем. В любом случае, scipy.sparse как (если не главный) основной предмет для последующих обсуждений продолжает подниматься в обсуждениях, касающихся будущего matrix. - person Andras Deak; 12.11.2018
comment
@hpaulj нашел. Основная проблема, с которой я столкнулся с удалением матриц numpy, - это влияние на scipy.sparse, который в основном постоянно имитирует семантику numpy.matrix и часто дает результаты numpy.matrix при уплотнении. с 2017 года, на что Ральф ответил < i> Я думаю, что мы застряли на scipy.sparse и, возможно, в какой-то момент добавим новую реализацию sparse * array * рядом с ним. Для scipy нам нужно будет добавить зависимость от нового пакета npmatrix или поставить его поставщиком. - person Andras Deak; 12.11.2018
comment
Суммы по строкам и столбцам разреженных матриц действительно возвращают плотные матрицы. Мне пришлось бы проверить реализацию, но я сомневаюсь, что это глубокая зависимость. - person hpaulj; 12.11.2018
comment
Слава богу, как человеку, который больше любит приложения numpy. Между синтаксическим анализом кода и поиском ошибок, основанных на объединении ndarray и matrix, и попытками сделать тензорную алгебру более высокой размерности с помощью языка, который часто, кажется, предполагает, что 2D matrix достаточно хорош, эта бифуркация была огромной головной болью с тех пор, как я начал использовать numpy. Большое спасибо тем, кто выполняет сложное кодирование, которое, я знаю, должно происходить в фоновом режиме, чтобы это сделать. - person Daniel F; 12.11.2018
comment
Мне особенно нравится бесконечность = 32 - person pipe; 12.11.2018
comment
Как кто-то из Matlab и кто будет много использовать numpy для линейной алгебры, мне интересно, есть ли учебник, как сделать эффективный и элегантный LA с numpy, тем более что никто не должен используйте 1_? Я уже спотыкаюсь о таких простых вещах, как создание вектора-столбца. Может быть, я смогу обойтись без них, но я очень ценю концептуальную ясность, которая приходит с различием между векторами строк и столбцов. - person A. Donda; 21.05.2019
comment
@ A.Donda, по моему опыту, вы будете намного более продуктивны, если откажетесь от векторов строк и столбцов. Например, v @ A и A @ v оба работают для 1d массивов v, я полагаю, так же, как если бы они были векторами строк / столбцов, соответственно. Для более сложных вещей вам понадобится либо широковещательная передача, либо einsum, опять же с использованием другой логики. Но я понимаю, что к этому нужно привыкнуть (хотя лично я переключился очень легко и никогда не оглядывался назад). - person Andras Deak; 21.05.2019
comment
@AndrasDeak, может я ошибаюсь, но я не только программирую линейную алгебру, я думаю в этом. Я разрабатываю методы анализа данных с использованием символьных математических выводов с использованием линейной алгебры, а затем реализую их. С этой точки зрения я бы предпочел, чтобы одно из этих двух выражений с @ не работало, потому что соответствующее математическое выражение не имеет смысла. Что мне нравится в Matlab, так это то, что я могу читать код почти так же, как читаю математику, и если код не работает (выдает ошибку), он говорит мне, что я ошибался. Это очень ценно для меня. - person A. Donda; 21.05.2019
comment
@ A.Donda подумал: вы можете использовать массивы формы (1,n) и (n,1) для ограничения операций так же, как вы хотели, чтобы класс matrix работал. Рассмотрим vrow = np.random.rand(3)[None,:]; vcol = np.random.rand(3)[:,None]; M = np.random.rand(3,3). Результирующие массивы будут подчиняться только линейной алгебре, и одноэлементные размеры будут сохранены, поэтому vrow @ vcol - это двумерный массив формы (1,1), а vcol @ vrow - это двумерный массив формы (3,3). Использование матрицы вместо векторной точки может привести к некоторому снижению производительности, но семантика должна быть сохранена в том виде, в каком вы предпочитаете. - person Andras Deak; 22.05.2019
comment
Это похоже на то, что я искал, спасибо! Итак, я могу предположить, что 2D-массив ведет себя как матрица, и когда у меня есть одномерный массив, я могу преобразовать его в вектор-строку или вектор-столбец, используя [None, :] и [:, None] соответственно? - person A. Donda; 22.05.2019
comment
Кстати. так как я совмещал ваш ответ, если хотите, я могу опубликовать новый вопрос, и вы можете опубликовать свой комментарий в качестве ответа. - person A. Donda; 22.05.2019
comment
@ A.Donda относительно вашего первого вопроса: да, 2d-массив (с одним одноэлементным измерением) является матрицей. Но могут быть сюрпризы, например, если фрагмент (строка или столбец) правильной матрицы будет 1d-массивом, вам нужно позаботиться о том, чтобы не потерять размеры. И да, это сокращение от введения одноэлементных измерений (возможно, вы бы предпочли, чтобы явные onedarray.reshape(1,-1) и ...(-1,1) говорили то же самое). Что касается вашего второго комментария: спасибо, но, насколько я понимаю, я в порядке, как и мы (возможно, уже есть дубликаты такого вопроса). - person Andras Deak; 23.05.2019