6.13. 선형 대수

카메라에서의 선형 대수는 작은 행렬 작업입니다: IMU 샘플을 월드 프레임에 융합하는 3x3 회전, 렌즈를 보정하는 보정 행렬, 칼만 필터의 상태-공분산 업데이트, 정규 방정식이 작은 선형 풀이로 나오는 다항식 적합 등입니다. numpy.linalgscipy.linalg 서브모듈은 바로 그 규모를 다룹니다.

이 함수들은 두 모듈에 들어 있습니다. 행렬 곱 연산은 numpy 최상위 레벨에 있고, 분해와 행렬 역은 numpy.linalg 아래에 있으며, 전용 선형 시스템 풀이는 scipy.linalg 아래에 있습니다. 예를 들어 2x2 행렬 곱은 다음과 같습니다:

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. 사용 가능한 것

  • dot() – 행렬 또는 벡터 곱.

  • cross() – 3차원 벡터 외적.

  • trace() – 대각합.

  • inv() – 행렬 역.

  • det() – 행렬식.

  • cholesky() – 촐레스키 분해(대칭 양의 정부호 입력).

  • eig() – 실수 대칭 행렬의 고유값과 고유벡터.

  • norm() – 벡터 또는 행렬의 2-노름.

  • qr()mode='reduced' (기본값) 또는 mode='complete' 를 사용한 QR 분해.

  • solve_triangular()A 가 삼각 행렬일 때 A @ x = b 를 풉니다.

  • cho_solve()A 의 촐레스키 인수가 주어졌을 때 A @ x = b 를 풉니다.

6.13.2. dot, cross, trace

dot() 은 카메라에서 행렬 곱셈을 작성하는 방법입니다. 데스크톱 numpy 가 동일한 작업을 위해 제공하는 @ 연산자는 ndarray 에 구현되어 있지 않으므로, 모든 행렬-벡터, 행렬-행렬, 벡터 내적은 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

dot() 의 결과는 항상 dtype float 입니다. cross() 는 두 3-벡터의 외적이고, trace() 는 정방 행렬의 주대각의 합입니다.

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

역은 가우스-조던 소거법으로 계산되므로, 행렬이 특이(singular)할 때 inv()ValueError 를 발생시킵니다(소거 중에 대각 요소가 0이 됨). RAM 비용은 대략 입력 크기의 두 배입니다.

행렬식은 동일한 소거를 재사용합니다 – 실행 시간은 본질적으로 역과 같습니다.

애플리케이션의 목표가 선형 시스템을 푸는 것일 때는, 역을 구해서 곱하지 마십시오 – 아래의 전용 풀이를 선호하십시오. 둘 다 더 빠르고 수치적으로 더 잘 작동합니다.

6.13.4. cholesky

대칭 양의 정부호 행렬 A 에 대해, cholesky()A = L @ L.T 가 되는 하삼각 L 을 반환합니다:

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

입력이 양의 정부호가 아니거나 대칭이 아니면 ValueError 가 발생합니다.

촐레스키 인수는 LU 분해의 절반에 해당하는 작업량이며, 행렬이 대칭 양의 정부호임이 알려진 모든 문제(공분산 업데이트, 최소제곱 적합에서 나오는 정규 방정식)에 대한 올바른 출발점입니다.

6.13.5. eig

eig()실수 대칭 행렬에서만 작동합니다. 비대칭 행렬은 ValueError 를 발생시킵니다. 이것은 2-튜플 (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)

참고:

  • 고유값은 특정 순서 없이 반환됩니다. 정렬된 순서가 중요할 때는 sort() 를 적용하십시오(그리고 argsort() 를 통해 고유벡터에도 동일한 순열을 적용하십시오).

  • 고유벡터는 0이 아닌 스칼라 배율까지만 유일하므로, 개별 고유벡터의 부호 는 유일하게 정의되지 않습니다. 두 번의 올바른 실행이 부호가 반대인 벡터를 만들어낼 수 있으나, 이는 무해합니다.

6.13.6. norm

벡터 또는 행렬의 유클리드(프로베니우스) 노름:

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

선택적 axis= 키워드는 전체 배열이 아니라 단일 축을 따라 노름을 취합니다.

6.13.7. qr

qr() 는 직사각 행렬 A (형태 (M, N))를 A == Q @ R 가 되는 정규 직교 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)

이 분해는 연속적인 기븐스 회전을 통해 구현됩니다. 행렬이 대칭이 아닌 최소제곱 문제에 적합한 선택입니다.

6.13.8. 시스템 풀이

ulab.scipy.linalg 아래의 두 전용 풀이는 np.dot(np.linalg.inv(A), b) 보다 둘 다 더 빠르고 더 정확합니다:

  • solve_triangular(a, b, lower=False)()a 가 삼각 행렬이라고 가정하고 a @ x = b 를 풉니다:

    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)() – 촐레스키 인수 L 이 주어졌을 때 A = L @ L.TA @ x = b 를 풉니다:

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

A 의 구조가 허용할 때마다 역을 구하는 대신 이것들을 활용하십시오 – 소거 작업 그리고 명시적 역 계산을 절약합니다.

6.13.9. 작은 선형 시스템 풀기

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

동일한 문제를 A 를 분해하고 적절한 풀이를 호출하여 표현할 수도 있습니다 – A 가 적절한 구조를 가질 때 더 빠르고 더 정확합니다.

완전한 인수 수준 참조는 numpy.linalg — 선형대수 루틴scipy.linalg — 선형 대수 루틴 를 참조하십시오.