czym różni się mnożenie dla klas NumPy Matrix vs Array?

131

Numpy docs zalecają używanie tablicy zamiast matrix do pracy z macierzami. Jednak w przeciwieństwie do oktawy (której używałem do niedawna), * nie wykonuje mnożenia macierzy, musisz użyć funkcji matrixmultipy (). Czuję, że to sprawia, że ​​kod jest bardzo nieczytelny.

Czy ktoś podziela moje poglądy i znalazł rozwiązanie?

elexhobby
źródło
8
Pytasz o opinie, a nie o pytanie. Czy jest coś bardziej konkretnego, w czym moglibyśmy pomóc, a może pomóc w uczynieniu go bardziej czytelnym?
Wheaties
2
Właściwie dokumentacja zaleca używanie macierzy, jeśli robisz algebrę liniową i nie chcesz używać funkcji multiply (), więc w czym problem?
Matti Pastell
1
Nie przejrzałem szczegółowo dokumentacji. Ciekawe, jakie zalety oferują tablice w porównaniu z klasą macierzy? Odkryłem, że tablice nie rozróżniają wierszy i kolumn. Czy to dlatego, że tablice mają być traktowane jako tensory, a nie macierze? Jak zauważył Joe, fakt, że klasa matrix jest 2-dim jest dość ograniczony. Jakie jest myślenie stojące za tego rodzaju projektami, jak w przypadku, dlaczego nie mieć jednej klasy macierzy, takiej jak matlab / octave?
elexhobby
Myślę, że głównym problemem jest to, że Python nie ma .*składni vs '*' dla mnożenia elementów vs mnożenia macierzy. Gdyby tak było, wszystko byłoby prostsze, chociaż jestem zaskoczony, że wybrali *na myśli elementy, a nie mnożenie macierzy.
Charlie Parker

Odpowiedzi:

128

Głównym powodem unikania używania tej matrixklasy jest to, że a) jest z natury dwuwymiarowa oraz b) ma dodatkowe obciążenie w porównaniu z „normalną” tablicą numpy. Jeśli wszystko, co robisz, to algebra liniowa, to zdecydowanie możesz użyć klasy macierzy ... Osobiście uważam, że jest to bardziej kłopotliwe niż warte.

W przypadku tablic (starszych niż Python 3.5) użyj dotzamiast matrixmultiply.

Na przykład

import numpy as np
x = np.arange(9).reshape((3,3))
y = np.arange(3)

print np.dot(x,y)

Lub w nowszych wersjach numpy po prostu użyj x.dot(y)

Osobiście uważam to za znacznie bardziej czytelne niż *operator implikujący mnożenie macierzy ...

W przypadku tablic w Pythonie 3.5 użyj x @ y.

Joe Kington
źródło
10
Jest nieczytelny, gdy masz stos mnożenia, na przykład x ' A' * A x.
elexhobby
14
@elexhobby - czy x.T.dot(A.T).dot(A).dot(x)to nie jest nieczytelne, imo Jednak dla każdego z nich. Jeśli zajmujesz się głównie mnożeniem macierzy, zdecydowanie użyj numpy.matrix!
Joe Kington,
7
A propos, dlaczego mnożenie macierzy nazywa się „kropką”? W jakim sensie jest to iloczyn skalarny?
amcnabb
8
@amcnabb - Mnożenie macierzy jest czasami określane jako „iloczyn skalarny” w podręcznikach (w tych książkach iloczyn skalarny, o którym myślisz, nazywany jest „iloczynem skalarnym” lub „iloczynem skalarnym”). W końcu iloczyn skalarny to po prostu mnożenie macierzy przez dwa wektory, więc używanie „kropki” do oznaczania mnożenia macierzy w ogóle nie jest zbyt trudne. Ten konkretny zapis wydaje się (?) Bardziej powszechny w tekstach inżynierskich i naukowych niż w matematyce, przynajmniej z mojego doświadczenia. Jego częstość występowania u numpy wynika głównie z trudności w pisaniu numpy.matrixmultiply.
Joe Kington,
7
@amcnabb Chodzi o to, że kropka uogólnia się na dowolną wymiarowość bez niejasności. To właśnie sprawia, że numpy.dotmnożenie macierzy jest równoważne. Jeśli naprawdę nie podoba ci się notacja, użyj matrixklasy.
Henry Gomersall
81

kluczowe rzeczy, które należy wiedzieć o operacjach na tablicach NumPy w porównaniu z operacjami na macierzach NumPy to:

  • Macierz NumPy jest podklasą tablicy NumPy

  • NumPy tablicy operacje są elementem mądry (raz transmisja jest wykazywana)

  • Operacje na macierzach NumPy podlegają zwykłym regułom algebry liniowej

kilka fragmentów kodu do zilustrowania:

>>> from numpy import linalg as LA
>>> import numpy as NP

>>> a1 = NP.matrix("4 3 5; 6 7 8; 1 3 13; 7 21 9")
>>> a1
matrix([[ 4,  3,  5],
        [ 6,  7,  8],
        [ 1,  3, 13],
        [ 7, 21,  9]])

>>> a2 = NP.matrix("7 8 15; 5 3 11; 7 4 9; 6 15 4")
>>> a2
matrix([[ 7,  8, 15],
        [ 5,  3, 11],
        [ 7,  4,  9],
        [ 6, 15,  4]])

>>> a1.shape
(4, 3)

>>> a2.shape
(4, 3)

>>> a2t = a2.T
>>> a2t.shape
(3, 4)

>>> a1 * a2t         # same as NP.dot(a1, a2t) 
matrix([[127,  84,  85,  89],
        [218, 139, 142, 173],
        [226, 157, 136, 103],
        [352, 197, 214, 393]])

ale ta operacja kończy się niepowodzeniem, jeśli te dwie macierze NumPy zostaną przekonwertowane na tablice:

>>> a1 = NP.array(a1)
>>> a2t = NP.array(a2t)

>>> a1 * a2t
Traceback (most recent call last):
   File "<pyshell#277>", line 1, in <module>
   a1 * a2t
   ValueError: operands could not be broadcast together with shapes (4,3) (3,4) 

chociaż użycie składni NP.dot działa z tablicami ; te operacje działają jak mnożenie macierzy:

>> NP.dot(a1, a2t)
array([[127,  84,  85,  89],
       [218, 139, 142, 173],
       [226, 157, 136, 103],
       [352, 197, 214, 393]])

więc czy kiedykolwiek potrzebujesz macierzy NumPy? tj. czy tablica NumPy wystarczy do obliczenia algebry liniowej (pod warunkiem, że znasz poprawną składnię, np. NP.dot)?

zasada wydaje się być taka, że ​​jeśli argumenty (tablice) mają kształty (mxn) zgodne z daną operacją algebry liniowej, to wszystko jest w porządku, w przeciwnym razie rzuca NumPy.

jedynym wyjątkiem, na jaki się natknąłem (prawdopodobnie są inne), jest odwrotne obliczanie macierzy .

poniżej znajdują się fragmenty, w których nazwałem operację czystej algebry liniowej (w rzeczywistości z modułu Linear Algebra Numpy'ego) i przekazane w tablicy NumPy

wyznacznik tablicy:

>>> m = NP.random.randint(0, 10, 16).reshape(4, 4)
>>> m
array([[6, 2, 5, 2],
       [8, 5, 1, 6],
       [5, 9, 7, 5],
       [0, 5, 6, 7]])

>>> type(m)
<type 'numpy.ndarray'>

>>> md = LA.det(m)
>>> md
1772.9999999999995

wektory własne / pary wartości własnych :

>>> LA.eig(m)
(array([ 19.703+0.j   ,   0.097+4.198j,   0.097-4.198j,   5.103+0.j   ]), 
array([[-0.374+0.j   , -0.091+0.278j, -0.091-0.278j, -0.574+0.j   ],
       [-0.446+0.j   ,  0.671+0.j   ,  0.671+0.j   , -0.084+0.j   ],
       [-0.654+0.j   , -0.239-0.476j, -0.239+0.476j, -0.181+0.j   ],
       [-0.484+0.j   , -0.387+0.178j, -0.387-0.178j,  0.794+0.j   ]]))

norma macierzy :

>>>> LA.norm(m)
22.0227

rozkład na czynniki qr :

>>> LA.qr(a1)
(array([[ 0.5,  0.5,  0.5],
        [ 0.5,  0.5, -0.5],
        [ 0.5, -0.5,  0.5],
        [ 0.5, -0.5, -0.5]]), 
 array([[ 6.,  6.,  6.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]]))

ranga matrycy :

>>> m = NP.random.rand(40).reshape(8, 5)
>>> m
array([[ 0.545,  0.459,  0.601,  0.34 ,  0.778],
       [ 0.799,  0.047,  0.699,  0.907,  0.381],
       [ 0.004,  0.136,  0.819,  0.647,  0.892],
       [ 0.062,  0.389,  0.183,  0.289,  0.809],
       [ 0.539,  0.213,  0.805,  0.61 ,  0.677],
       [ 0.269,  0.071,  0.377,  0.25 ,  0.692],
       [ 0.274,  0.206,  0.655,  0.062,  0.229],
       [ 0.397,  0.115,  0.083,  0.19 ,  0.701]])
>>> LA.matrix_rank(m)
5

stan matrycy :

>>> a1 = NP.random.randint(1, 10, 12).reshape(4, 3)
>>> LA.cond(a1)
5.7093446189400954

inwersja wymaga jednak macierzy NumPy:

>>> a1 = NP.matrix(a1)
>>> type(a1)
<class 'numpy.matrixlib.defmatrix.matrix'>

>>> a1.I
matrix([[ 0.028,  0.028,  0.028,  0.028],
        [ 0.028,  0.028,  0.028,  0.028],
        [ 0.028,  0.028,  0.028,  0.028]])
>>> a1 = NP.array(a1)
>>> a1.I

Traceback (most recent call last):
   File "<pyshell#230>", line 1, in <module>
   a1.I
   AttributeError: 'numpy.ndarray' object has no attribute 'I'

ale pseudoinverse Moore'a-Penrose'a wydaje się działać dobrze

>>> LA.pinv(m)
matrix([[ 0.314,  0.407, -1.008, -0.553,  0.131,  0.373,  0.217,  0.785],
        [ 1.393,  0.084, -0.605,  1.777, -0.054, -1.658,  0.069, -1.203],
        [-0.042, -0.355,  0.494, -0.729,  0.292,  0.252,  1.079, -0.432],
        [-0.18 ,  1.068,  0.396,  0.895, -0.003, -0.896, -1.115, -0.666],
        [-0.224, -0.479,  0.303, -0.079, -0.066,  0.872, -0.175,  0.901]])

>>> m = NP.array(m)

>>> LA.pinv(m)
array([[ 0.314,  0.407, -1.008, -0.553,  0.131,  0.373,  0.217,  0.785],
       [ 1.393,  0.084, -0.605,  1.777, -0.054, -1.658,  0.069, -1.203],
       [-0.042, -0.355,  0.494, -0.729,  0.292,  0.252,  1.079, -0.432],
       [-0.18 ,  1.068,  0.396,  0.895, -0.003, -0.896, -1.115, -0.666],
       [-0.224, -0.479,  0.303, -0.079, -0.066,  0.872, -0.175,  0.901]])
Doug
źródło
3
mInv = NP.linalg.inv (m) oblicza odwrotność tablicy
db1234
Ważnym punktem, na który należy zwrócić uwagę, jest to, że * jest mnożeniem elementarnym, a kropka jest prawdziwym mnożeniem macierzy. Zobacz stackoverflow.com/a/18255635/1780570
Minh Triet
Uwaga IMP: należy unikać numpy macierzy na rzecz tablic. Uwaga z dokumentacji -> „Nie zaleca się już używania tej klasy, nawet w przypadku algebry liniowej. Zamiast tego należy używać zwykłych tablic. W przyszłości klasa może zostać usunięta”. Zobacz także stackoverflow.com/a/61156350/6043669
HopeKing
21

W 3.5 Python w końcu uzyskał operator mnożenia macierzy . Składnia to a @ b.

Petr Viktorin
źródło
2
Dzięki! Tak, cieszę się, że nie jestem jedyną osobą, która czuje, że obecny zapis jest nieczytelny.
elexhobby
15

Jest sytuacja, w której operator kropkowy będzie udzielał innych odpowiedzi podczas pracy z tablicami, jak w przypadku macierzy. Załóżmy na przykład, że:

>>> a=numpy.array([1, 2, 3])
>>> b=numpy.array([1, 2, 3])

Przekształćmy je w macierze:

>>> am=numpy.mat(a)
>>> bm=numpy.mat(b)

Teraz możemy zobaczyć inne dane wyjściowe dla dwóch przypadków:

>>> print numpy.dot(a.T, b)
14
>>> print am.T*bm
[[1.  2.  3.]
 [2.  4.  6.]
 [3.  6.  9.]]
Jadiel de Armas
źródło
Mówiąc konkretnie, * jest mnożeniem elementarnym, kropka jest prawdziwym mnożeniem macierzy. Zobacz stackoverflow.com/a/18255635/1780570
Minh Triet
Dzieje się tak, ponieważ jako tablica numpy, aT == a, transpozycja nic nie robi.
patapouf_ai
Jeśli napiszesz w = np.array ([[1], [2], [3]]), to numpy.dot (at, b) powinien dać ci to samo. Różnica między matixem a tablicą nie polega na kropce, ale na transpozycji.
patapouf_ai
A właściwie, jeśli napiszesz a = numpy.array ([[1,2,3]]), to aT naprawdę się transponuje i wszystko będzie działać tak jak w macierzach.
patapouf_ai
8

Źródła z http://docs.scipy.org/doc/scipy/reference/tutorial/linalg.html

..., korzystanie z numpy.matrix klasie jest zniechęcony , ponieważ nie wnosi nic nowego, że nie można osiągnąć z 2D numpy.ndarray obiektów i może prowadzić do nieporozumień , których klasa jest używany. Na przykład,

>>> import numpy as np
>>> from scipy import linalg
>>> A = np.array([[1,2],[3,4]])
>>> A
    array([[1, 2],
           [3, 4]])
>>> linalg.inv(A)
array([[-2. ,  1. ],
      [ 1.5, -0.5]])
>>> b = np.array([[5,6]]) #2D array
>>> b
array([[5, 6]])
>>> b.T
array([[5],
      [6]])
>>> A*b #not matrix multiplication!
array([[ 5, 12],
      [15, 24]])
>>> A.dot(b.T) #matrix multiplication
array([[17],
      [39]])
>>> b = np.array([5,6]) #1D array
>>> b
array([5, 6])
>>> b.T  #not matrix transpose!
array([5, 6])
>>> A.dot(b)  #does not matter for multiplication
array([17, 39])

Operacje scipy.linalg można zastosować w równym stopniu do obiektów numpy.matrix lub 2D numpy.ndarray .

Yong Yang
źródło
7

Ta sztuczka może być tym, czego szukasz. Jest to rodzaj prostego przeciążenia operatora.

Możesz wtedy użyć czegoś podobnego do sugerowanej klasy Infix w następujący sposób:

a = np.random.rand(3,4)
b = np.random.rand(4,3)
x = Infix(lambda x,y: np.dot(x,y))
c = a |x| b
Bitowo
źródło
5

Odpowiedni cytat z PEP 465 - Dedykowany operator wrostek do mnożenia macierzy , jak wspomniał @ petr-viktorin, wyjaśnia problem, z którym miał do czynienia PO:

[...] numpy udostępnia dwa różne typy z różnymi __mul__metodami. W przypadku numpy.ndarrayobiektów *wykonuje mnożenie elementarne, a mnożenie macierzy musi używać funkcji call ( numpy.dot). W przypadku numpy.matrixobiektów *wykonuje mnożenie macierzy, a mnożenie elementarne wymaga składni funkcji. Pisanie kodu przy użyciu numpy.ndarraydziała dobrze. Pisanie kodu przy użyciu numpy.matrixrównież działa dobrze. Ale kłopoty zaczynają się, gdy tylko spróbujemy zintegrować te dwa fragmenty kodu razem. Kod, który oczekuje ndarrayi otrzyma a matrixlub odwrotnie, może ulec awarii lub zwrócić nieprawidłowe wyniki

Wprowadzenie @operatora infix powinno pomóc ujednolicić i uprościć kod macierzy Pythona.

cod3monk3y
źródło
1

Funkcja matmul (od numpy 1.10.1) działa dobrze dla obu typów i zwraca wynik jako numpy matrix matrix:

import numpy as np

A = np.mat('1 2 3; 4 5 6; 7 8 9; 10 11 12')
B = np.array(np.mat('1 1 1 1; 1 1 1 1; 1 1 1 1'))
print (A, type(A))
print (B, type(B))

C = np.matmul(A, B)
print (C, type(C))

Wynik:

(matrix([[ 1,  2,  3],
        [ 4,  5,  6],
        [ 7,  8,  9],
        [10, 11, 12]]), <class 'numpy.matrixlib.defmatrix.matrix'>)
(array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]]), <type 'numpy.ndarray'>)
(matrix([[ 6,  6,  6,  6],
        [15, 15, 15, 15],
        [24, 24, 24, 24],
        [33, 33, 33, 33]]), <class 'numpy.matrixlib.defmatrix.matrix'>)

Od czasu Pythona 3.5, jak wspomniano wcześniej, możesz również użyć nowego operatora mnożenia macierzy, @takiego jak

C = A @ B

i uzyskaj taki sam wynik jak powyżej.

Spokój
źródło