6.13. Algebra lineare#

L’algebra lineare su una camera è lavoro su piccole matrici: una rotazione 3x3 che fonde un campione della IMU nel frame del mondo, una matrice di calibrazione che rettifica una lente, l’aggiornamento di stato-covarianza di un filtro di Kalman, un fit polinomiale le cui equazioni normali si riducono a una minuscola soluzione lineare. I sottomoduli numpy.linalg e scipy.linalg coprono esattamente questa scala.

Le funzioni risiedono in due moduli. Le operazioni di prodotto tra matrici si trovano al livello superiore di numpy; le decomposizioni e l’inversa di matrice sono sotto numpy.linalg; i risolutori dedicati di sistemi lineari sono sotto scipy.linalg. Una moltiplicazione di matrici 2-per-2, ad esempio:

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. Cosa è disponibile#

  • dot() – prodotto matriciale o vettoriale.

  • cross() – prodotto vettoriale 3-D.

  • trace() – somma della diagonale.

  • inv() – inversa di matrice.

  • det() – determinante.

  • cholesky() – decomposizione di Cholesky (input simmetrico definito positivo).

  • eig() – autovalori e autovettori di una matrice reale simmetrica.

  • norm() – norma 2 di un vettore o di una matrice.

  • qr() – decomposizione QR con mode='reduced' (predefinita) o mode='complete'.

  • solve_triangular() – risolve A @ x = b quando A è triangolare.

  • cho_solve() – risolve A @ x = b dato un fattore di Cholesky di A.

6.13.2. dot, cross, trace#

dot() è il modo in cui si scrive la moltiplicazione di matrici sulla camera. L’operatore @ che il numpy desktop fornisce per lo stesso scopo non è implementato su ndarray, quindi ogni prodotto matrice-vettore, matrice-matrice e prodotto scalare tra vettori passa per la chiamata 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

Il risultato di dot() è sempre di dtype float. cross() è il prodotto vettoriale di due vettori a 3 componenti, trace() la somma della diagonale principale di una matrice quadrata.

6.13.3. inv e 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))

L’inversa viene calcolata mediante eliminazione di Gauss-Jordan, quindi inv() solleva ValueError quando la matrice è singolare (un elemento diagonale diventa zero durante l’eliminazione). Il costo in RAM è all’incirca il doppio della dimensione dell’input.

Il determinante riutilizza la stessa eliminazione – il tempo di esecuzione è essenzialmente lo stesso dell’inversa.

Quando l’obiettivo dell’applicazione è risolvere un sistema lineare, non invertire e moltiplicare – preferisci i risolutori dedicati riportati sotto. Entrambi sono più veloci e numericamente più affidabili.

6.13.4. cholesky#

Per una matrice simmetrica definita positiva A, cholesky() restituisce una matrice triangolare inferiore L tale che A = L @ L.T

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

Se l’input non è definito positivo o non è simmetrico, viene sollevato ValueError.

Il fattore di Cholesky richiede metà del lavoro di una fattorizzazione LU ed è il punto di partenza giusto per qualsiasi problema la cui matrice sia nota essere simmetrica definita positiva (aggiornamenti di covarianza, equazioni normali da un fit ai minimi quadrati).

6.13.5. eig#

eig() funziona solo su matrici reali simmetriche. Le matrici non simmetriche sollevano ValueError. Restituisce una 2-tupla (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)

Note:

  • Gli autovalori vengono restituiti senza un ordine particolare. Applica sort() (e la stessa permutazione agli autovettori tramite argsort()) quando l’ordinamento è importante.

  • Un autovettore è univoco solo a meno di uno scalare non nullo, quindi il segno dei singoli autovettori non è definito in modo univoco. Due esecuzioni corrette possono produrre vettori di segno opposto; questo è innocuo.

6.13.6. norm#

La norma euclidea (di Frobenius) di un vettore o di una matrice:

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...

La parola chiave opzionale axis= calcola la norma lungo un singolo asse invece che sull’intero array.

6.13.7. qr#

qr() fattorizza una matrice rettangolare A (forma (M, N)) in una matrice ortonormale Q e una matrice triangolare superiore R tali che 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)

La decomposizione è implementata tramite successive rotazioni di Givens. È la scelta giusta per problemi ai minimi quadrati in cui la matrice non è simmetrica.

6.13.8. Risolvere i sistemi#

I due risolutori dedicati sotto ulab.scipy.linalg sono entrambi più veloci e più accurati di np.dot(np.linalg.inv(A), b):

  • solve_triangular(a, b, lower=False)() – risolve a @ x = b assumendo che a sia triangolare:

    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)() – dato un fattore di Cholesky L, risolve A @ x = b dove A = L @ L.T

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

Ricorri a questi invece di invertire ogni volta che la struttura di A lo consente – risparmiano lavoro di eliminazione e l’inversa esplicita.

6.13.9. Risolvere un piccolo sistema lineare#

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

Lo stesso problema può essere espresso fattorizzando A e chiamando il risolutore appropriato – più veloce e più accurato quando A ha la struttura giusta.

Per il riferimento completo a livello di argomenti, vedi numpy.linalg — Routine di algebra lineare e scipy.linalg — Routine di algebra lineare.