6.13. Linjär algebra#

Linjär algebra på en kamera är arbete med små matriser: en 3x3-rotation som smälter in ett IMU-sampel i världskoordinatsystemet, en kalibreringsmatris som rätar upp ett objektiv, en Kalman-filters tillstånds-kovariansuppdatering, en polynomanpassning vars normalekvationer kommer ut som ett litet linjärt lösningsproblem. Submodulerna numpy.linalg och scipy.linalg täcker exakt den skalan.

Funktionerna finns i två moduler. Matrisproduktoperationer ligger på toppnivån i numpy; dekompositioner och matrisinversen ligger under numpy.linalg; de dedikerade lösarna för linjära system ligger under scipy.linalg. En 2-gånger-2-matrismultiplikation, till exempel:

from ulab import numpy as np

A = np.array([[1, 2], [3, 4]], dtype=np.float)
B = np.array([[5, 6], [7, 8]], dtype=np.float)
np.dot(A, B)
# array([[19.0, 22.0],
#        [43.0, 50.0]])

6.13.1. Vad som finns tillgängligt#

  • dot() – matris- eller vektorprodukt.

  • cross() – 3D-vektors kryssprodukt.

  • trace() – summan av diagonalen.

  • inv() – matrisinvers.

  • det() – determinant.

  • cholesky() – Cholesky-dekomposition (symmetrisk positivt definit indata).

  • eig() – egenvärden och egenvektorer för en reell symmetrisk matris.

  • norm() – 2-norm för en vektor eller matris.

  • qr() – QR-dekomposition med mode='reduced' (standard) eller mode='complete'.

  • solve_triangular() – lös A @ x = b när A är triangulär.

  • cho_solve() – lös A @ x = b givet en Cholesky-faktor av A.

6.13.2. dot, cross, trace#

dot() är hur matrismultiplikation skrivs på kameran. Operatorn @ som skrivbords-numpy tillhandahåller för samma uppgift är inte implementerad på ndarray, så varje matris-vektor-, matris-matris- och vektorskalärprodukt går genom anropet dot()

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

np.dot(a, b)             # 32.0 (scalar product)
np.cross(a, b)           # array([-3.0, 6.0, -3.0])

m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.dot(m, a)             # matrix-vector product
np.dot(m, m)             # matrix-matrix product
np.trace(m)              # 1 + 5 + 9 = 15.0

Resultatet av dot() är alltid av dtype float. cross() är kryssprodukten av två 3-vektorer, trace() summan av huvuddiagonalen i en kvadratisk matris.

6.13.3. inv och det#

m = np.array([[1, 2, 3, 4],
              [4, 5, 6, 4],
              [7, 9, 9, 4],
              [3, 4, 5, 6]])
print(np.linalg.inv(m))
print(np.linalg.det(m))

Inversen beräknas med Gauss-Jordan-eliminering, så inv() ger upphov till ValueError när matrisen är singulär (en diagonalpost blir noll under elimineringen). RAM-kostnaden är ungefär dubbelt så stor som indatans storlek.

Determinanten återanvänder samma eliminering – körtiden är i princip densamma som för inversen.

När applikationens mål är att lösa ett linjärt system, invertera inte och multiplicera – föredra de dedikerade lösarna nedan. Båda är snabbare och bättre numeriskt uppförda.

6.13.4. cholesky#

För en symmetrisk positivt definit matris A returnerar cholesky() en nedre triangulär L så att A = L @ L.T

a = np.array([[25, 15, -5],
              [15, 18,  0],
              [-5,  0, 11]])
L = np.linalg.cholesky(a)

Om indatan inte är positivt definit eller inte symmetrisk ges ValueError.

Cholesky-faktorn är hälften av arbetet jämfört med en LU-faktorisering och är rätt utgångspunkt för varje problem vars matris är känd för att vara symmetrisk positivt definit (kovariansuppdateringar, normalekvationer från en minstakvadratanpassning).

6.13.5. eig#

eig() fungerar endast på reella symmetriska matriser. Icke-symmetriska matriser ger upphov till ValueError. Den returnerar en 2-tupel (eigenvalues, eigenvectors)

a = np.array([[1, 2, 1, 4],
              [2, 5, 3, 5],
              [1, 3, 6, 1],
              [4, 5, 1, 7]], dtype=np.uint8)
x, y = np.linalg.eig(a)

Anmärkningar:

  • Egenvärdena kommer tillbaka i ingen särskild ordning. Tillämpa sort() (och samma permutation på egenvektorerna via argsort()) när en sorterad ordning spelar roll.

  • En egenvektor är unik endast upp till en nollskild skalär, så tecknet hos enskilda egenvektorer är inte entydigt definierat. Två korrekta körningar kan producera vektorer med motsatt tecken; detta är ofarligt.

6.13.6. norm#

Den euklidiska (Frobenius) normen för en vektor eller matris:

v = np.array([1, 2, 3, 4, 5])
m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

np.linalg.norm(v)        # 7.416...
np.linalg.norm(m)        # 16.881...

Det valfria nyckelordet axis= tar normen längs en enda axel istället för över hela arrayen.

6.13.7. qr#

qr() faktoriserar en rektangulär matris A (form (M, N)) till en ortonormal Q och en övre triangulär R så att A == Q @ R

A = np.arange(6).reshape((3, 2))

q, r = np.linalg.qr(A)
# mode='reduced' (default): q is (3, 2), r is (2, 2)

q, r = np.linalg.qr(A, mode='complete')
# q is (3, 3), r is (3, 2)

Dekompositionen är implementerad via successiva Givens-rotationer. Rätt val för minstakvadratproblem där matrisen inte är symmetrisk.

6.13.8. Lösa system#

De två dedikerade lösarna under ulab.scipy.linalg är båda snabbare och mer exakta än np.dot(np.linalg.inv(A), b):

  • solve_triangular(a, b, lower=False)() – lös a @ x = b med antagandet att a är triangulär:

    A = np.array([[3, 0, 0, 0],
                  [2, 1, 0, 0],
                  [1, 0, 1, 0],
                  [1, 2, 1, 8]])
    b = np.array([4, 2, 4, 2])
    x = sp.linalg.solve_triangular(A, b, lower=True)
    
  • cho_solve(L, b)() – givet en Cholesky-faktor L, lös A @ x = b där A = L @ L.T

    L = np.linalg.cholesky(A)
    x = sp.linalg.cho_solve(L, b)
    

Ta till dessa istället för att invertera närhelst strukturen hos A tillåter – de sparar både elimineringsarbete och den explicita inversen.

6.13.9. Lösa ett litet linjärt system#

A = np.array([[3, 0, 1, 1],
              [0, 1, 0, 2],
              [1, 0, 1, 1],
              [1, 2, 1, 8]])
b = np.array([4, 2, 4, 2])

x = np.dot(np.linalg.inv(A), b)
print(x)
print(np.dot(A, x))         # should equal b

Samma problem kan uttryckas genom att faktorisera A och anropa den lämpliga lösaren – snabbare och mer exakt när A har rätt struktur.

För den fullständiga referensen på argumentnivå, se numpy.linalg — Rutiner för linjär algebra och scipy.linalg — Linjäralgebrarutiner.