L13 서술 파트에서 소거 한 번으로 얻는 이 나머지를 전부 결정하는 것을 보았고, 그것을 이라는 등식 하나로 묶었다.
이 노트북은 새 개념을 배우는 자리가 아니라 도구를 만드는 자리이다. 행렬을 넣으면
L1부터 L13까지의 모든 정보를 한 번에 돌려주는 행렬해부(A, b) 를 조립하고,
앞으로의 강의에서 계속 꺼내 쓰도록 하자.
| 만들 것 | 하는 일 |
|---|---|
기약사다리꼴(A) | 정확한 과 피벗 열 번호 |
랭크분해(A) | 의 두 조각 |
영공간행렬(A) | 특수해를 열로 세운 |
행렬해부(A, b) | 위의 것을 전부 묶은 최종 도구 |
0. 준비¶
import numpy as np
import sympy as sp
from scipy.linalg import null_space
from linalg_viz import show_matrix
np.set_printoptions(precision=3, suppress=True)
rng = np.random.default_rng(13)
print("numpy", np.__version__, " sympy", sp.__version__)numpy 2.5.2 sympy 1.14.0
L7부터 계속 쓰던 행렬을 그대로 가져온다. 서술 파트의 예와 같아야 대조하기 쉽다.
A = np.array([[1.0, 2, 2, 2],
[2.0, 4, 6, 8],
[3.0, 6, 8, 10]])
b = np.array([1.0, 5, 6])
print(show_matrix(A, "A ="))
print("크기 :", A.shape)A =
[ 1 2 2 2 ]
[ 2 4 6 8 ]
[ 3 6 8 10 ]
크기 : (3, 4)
1. 소거 한 번으로 얻는 것¶
sympy 는 분수를 그대로 다루므로 소거 과정에 반올림 오차가 끼지 않는다.
피벗 열 번호까지 함께 돌려주는 것이 요점이다.
def 기약사다리꼴(A):
"""정확한 기약 사다리꼴 R 과 피벗 열 번호를 돌려준다."""
M = sp.Matrix(np.asarray(A, dtype=float).tolist()).applyfunc(sp.Rational)
R_sym, 피벗 = M.rref()
return np.array(R_sym, dtype=float), list(피벗)R, 피벗 = 기약사다리꼴(A)
r = len(피벗)
n = A.shape[1]
자유 = [j for j in range(n) if j not in 피벗]
print(show_matrix(R, "R (기약 사다리꼴)"))
print("피벗열 :", [j + 1 for j in 피벗], " 자유열 :", [j + 1 for j in 자유])
print("랭크 r :", r)R (기약 사다리꼴)
[ 1 2 0 -2 ]
[ 0 0 1 2 ]
[ 0 0 0 0 ]
피벗열 : [1, 3] 자유열 : [2, 4]
랭크 r : 2
서술 파트 1절에서 이 이 세 가지를 동시에 센다고 했다. 하나씩 확인해 보자.
독립인행 = np.linalg.matrix_rank(A) # = 행공간의 차원
독립인열 = np.linalg.matrix_rank(A.T) # = 열공간의 차원
피벗변수 = len(피벗)
print("독립인 행의 개수 :", 독립인행)
print("독립인 열의 개수 :", 독립인열)
print("피벗 변수의 개수 :", 피벗변수)
print("셋이 모두 같은가 :", 독립인행 == 독립인열 == 피벗변수 == r)
print()
m = A.shape[0]
print(f"네 부분공간의 차원 : {r}, {n - r}, {r}, {m - r}")독립인 행의 개수 : 2
독립인 열의 개수 : 2
피벗 변수의 개수 : 2
셋이 모두 같은가 : True
네 부분공간의 차원 : 2, 2, 2, 1
2. ¶
는 의 피벗 열, 은 기약 사다리꼴에서 0인 행을 잘라 낸 조각이다.
def 랭크분해(A):
"""A = C R 로 쪼갠다. C 는 열공간의 기저, R 은 행공간의 기저이다."""
A = np.asarray(A, dtype=float)
Rref, 피벗 = 기약사다리꼴(A)
C = A[:, 피벗] # m x r : A 에서 그대로 떼어 온 피벗 열
R = Rref[:len(피벗)] # r x n : 0 인 행을 잘라 낸 것
return C, RC, R행 = 랭크분해(A)
print(show_matrix(C, "C (m x r)"))
print(show_matrix(R행, "R (r x n)"))
print("C R 이 A 와 같은가 :", np.allclose(C @ R행, A))C (m x r)
[ 1 2 ]
[ 2 6 ]
[ 3 8 ]
R (r x n)
[ 1 2 0 -2 ]
[ 0 0 1 2 ]
C R 이 A 와 같은가 : True
서술 파트에서 이 등식 하나가 네 가지를 말해 준다고 했다. 차례로 확인하자.
첫째, 의 열이 열공간의 기저이다. 의 열과 의 열을 나란히 붙여도 랭크가 늘지 않으면 같은 공간이다.
print("rank(C) :", np.linalg.matrix_rank(C))
print("rank([A | C]) :", np.linalg.matrix_rank(np.column_stack([A, C])),
" -> 같은 열공간")rank(C) : 2
rank([A | C]) : 2 -> 같은 열공간
둘째, 의 행이 행공간의 기저이다. 행끼리 붙여서 같은 검사를 한다.
print("rank(R) :", np.linalg.matrix_rank(R행))
print("rank([A ; R] 세로) :", np.linalg.matrix_rank(np.vstack([A, R행])),
" -> 같은 행공간")rank(R) : 2
rank([A ; R] 세로) : 2 -> 같은 행공간
셋째, 행랭크와 열랭크가 같다. 의 열 개수와 의 행 개수가 같은 수라는 것이 등식의 모양에서 바로 보인다.
print("C 의 열 개수 :", C.shape[1])
print("R 의 행 개수 :", R행.shape[0])
print("두 수가 같은가 :", C.shape[1] == R행.shape[0])C 의 열 개수 : 2
R 의 행 개수 : 2
두 수가 같은가 : True
넷째, 가 랭크 1 행렬 개의 합이다. 의 열과 의 행을 짝지어 바깥곱을 만들고 더하면 된다.
벽돌 = [np.outer(C[:, k], R행[k]) for k in range(r)]
for k, 조각 in enumerate(벽돌, 1):
print(show_matrix(조각, f"벽돌 {k} (rank {np.linalg.matrix_rank(조각)})"))
print("벽돌 합이 A 와 같은가 :", np.allclose(sum(벽돌), A))벽돌 1 (rank 1)
[ 1 2 0 -2 ]
[ 2 4 0 -4 ]
[ 3 6 0 -6 ]
벽돌 2 (rank 1)
[ 0 0 2 4 ]
[ 0 0 6 12 ]
[ 0 0 8 16 ]
벽돌 합이 A 와 같은가 : True
벽돌이 개보다 적으면 를 만들 수 없다. 랭크 1 행렬 개의 합은 랭크가 를 넘지 못하기 때문이다.
print("벽돌 1개만 :", np.linalg.matrix_rank(벽돌[0]))
print("벽돌 2개 합 :", np.linalg.matrix_rank(sum(벽돌)), " (= r =", r, ")")벽돌 1개만 : 1
벽돌 2개 합 : 2 (= r = 2 )
저장 공간¶
를 그대로 적으면 개, 와 로 나눠 적으면 개이다. 이 작으면 차이가 커진다.
큰A = rng.integers(-4, 5, (200, 3)) @ rng.integers(-4, 5, (3, 200))
큰A = 큰A.astype(float)
큰m, 큰n = 큰A.shape
큰r = np.linalg.matrix_rank(큰A)
print(f"크기 {큰m} x {큰n}, 랭크 {큰r}")
print(f"A 그대로 : {큰m * 큰n:,} 개")
print(f"C 와 R 로 : {큰r * (큰m + 큰n):,} 개")
print(f"줄어든 비율 : {큰m * 큰n / (큰r * (큰m + 큰n)):.1f} 배")크기 200 x 200, 랭크 3
A 그대로 : 40,000 개
C 와 R 로 : 1,200 개
줄어든 비율 : 33.3 배
3. 영공간의 기저¶
L7에서 만든 를 함수로 굳혀 둔다. 피벗 행과 자유 행을 제자리에 꽂아 넣으면 열 교환을 따로 되돌릴 필요가 없다.
def 영공간행렬(A):
"""특수해를 열로 세운 n x (n-r) 행렬을 돌려준다."""
A = np.asarray(A, dtype=float)
n = A.shape[1]
R, 피벗 = 기약사다리꼴(A)
r = len(피벗)
자유 = [j for j in range(n) if j not in 피벗]
F = R[:r][:, 자유] # r x (n-r)
N = np.zeros((n, n - r))
N[피벗, :] = -F # 피벗 변수는 끌려온다
N[자유, :] = np.eye(n - r) # 자유 변수에 1 을 하나씩
return N + 0.0 # -0.0 을 0.0 으로 정리N = 영공간행렬(A)
print(show_matrix(N, "N (열이 특수해)"))
print("A N 이 0 인가 :", np.allclose(A @ N, 0))
print("열의 개수 :", N.shape[1], " (n - r =", n - r, ")")N (열이 특수해)
[ -2 2 ]
[ 1 0 ]
[ 0 -2 ]
[ 0 1 ]
A N 이 0 인가 : True
열의 개수 : 2 (n - r = 2 )
L7에서 손으로 구한 특수해와 비교해 보자. , 이었다.
s1 = np.array([-2.0, 1, 0, 0])
s2 = np.array([2.0, 0, -2, 1])
print("N 의 열 :", N[:, 0] + 0.0, "/", N[:, 1] + 0.0)
print("손으로 :", s1, "/", s2)
print("같은가 :", np.allclose(N[:, 0], s1) and np.allclose(N[:, 1], s2))N 의 열 : [-2. 1. 0. 0.] / [ 2. 0. -2. 1.]
손으로 : [-2. 1. 0. 0.] / [ 2. 0. -2. 1.]
같은가 : True
scipy 의 null_space 는 정규직교 기저를 주므로 벡터 자체는 다르다.
같은 공간인지는 랭크로 확인해야 한다.
scipy기저 = null_space(A)
print(show_matrix(scipy기저, "scipy 의 기저"))
print("붙여도 랭크가 그대로인가 :",
np.linalg.matrix_rank(np.column_stack([N, scipy기저])) == N.shape[1])scipy 의 기저
[ 0.907 0.069 ]
[ -0.409 0.378 ]
[ -0.0893 -0.826 ]
[ 0.0446 0.413 ]
붙여도 랭크가 그대로인가 : True
4. 행렬해부 조립¶
지금까지 만든 것을 하나로 묶는다. 이 강의의 산출물이다.
def 행렬해부(A, b=None):
"""행렬 하나를 L1~L13 의 도구로 끝까지 분해해 사전으로 돌려준다."""
A = np.asarray(A, dtype=float)
m, n = A.shape
Rref, 피벗 = 기약사다리꼴(A)
r = len(피벗)
자유 = [j for j in range(n) if j not in 피벗]
정보 = {
"크기": (m, n), "랭크": r, "피벗열": 피벗, "자유열": 자유,
"차원": (r, n - r, r, m - r),
"열공간기저": A[:, 피벗], # C — A 에서 가져온다
"행공간기저": Rref[:r], # R — 기약 사다리꼴에서
"영공간기저": 영공간행렬(A),
"좌영공간기저": null_space(A.T),
"모든b에해": r == m,
"해가유일": r == n,
}
if b is not None:
b = np.asarray(b, dtype=float)
Rb, 피벗b = 기약사다리꼴(np.column_stack([A, b]))
if n in 피벗b: # 마지막 열이 피벗 -> 0 = 1 인 행
정보["해"] = "없음"
else:
특수해 = np.zeros(n)
for i, j in enumerate(피벗b):
특수해[j] = Rb[i, n]
정보["특수해"] = 특수해
정보["해"] = "유일" if r == n else "무한히 많음"
return 정보def 해부출력(A, b=None, 이름="A"):
"""행렬해부 의 결과를 사람이 읽기 좋게 찍는다."""
정보 = 행렬해부(A, b)
m, n = 정보["크기"]
r = 정보["랭크"]
d행, d영, d열, d좌영 = 정보["차원"]
print(f"[{이름}] {m} x {n}, 랭크 {r}")
print(f" 피벗열 {[j + 1 for j in 정보['피벗열']]}"
f" 자유열 {[j + 1 for j in 정보['자유열']]}")
print(f" 차원 행공간 {d행} | 영공간 {d영} | 열공간 {d열} | 좌영공간 {d좌영}")
print(f" 모든 b 에 해가 있는가 : {정보['모든b에해']} (r={r}, m={m})")
print(f" 해가 있다면 유일한가 : {정보['해가유일']} (r={r}, n={n})")
for 이름칸 in ("열공간기저", "행공간기저", "영공간기저", "좌영공간기저"):
기저 = 정보[이름칸]
개수 = 기저.shape[0] if 이름칸 == "행공간기저" else 기저.shape[1]
if 개수 == 0:
print(f" {이름칸} : 없음 (차원 0)")
else:
print(show_matrix(기저, f" {이름칸} ({개수}개)"))
if "해" in 정보:
print(f" Ax = b 의 해 : {정보['해']}")
if "특수해" in 정보:
print(f" 특수해 x_p = {정보['특수해'] + 0.0}")
print(f" 완전해 = x_p + (영공간의 아무 원소)")
return 정보_ = 해부출력(A, b, "L7 의 행렬")[L7 의 행렬] 3 x 4, 랭크 2
피벗열 [1, 3] 자유열 [2, 4]
차원 행공간 2 | 영공간 2 | 열공간 2 | 좌영공간 1
모든 b 에 해가 있는가 : False (r=2, m=3)
해가 있다면 유일한가 : False (r=2, n=4)
열공간기저 (2개)
[ 1 2 ]
[ 2 6 ]
[ 3 8 ]
행공간기저 (2개)
[ 1 2 0 -2 ]
[ 0 0 1 2 ]
영공간기저 (2개)
[ -2 2 ]
[ 1 0 ]
[ 0 -2 ]
[ 0 1 ]
좌영공간기저 (1개)
[ -0.577 ]
[ -0.577 ]
[ 0.577 ]
Ax = b 의 해 : 무한히 많음
특수해 x_p = [-2. 0. 1.5 0. ]
완전해 = x_p + (영공간의 아무 원소)
를 하나만 바꾸면 해가 사라진다. 서술 파트에서 열공간 밖으로 나간 경우이다.
_ = 해부출력(A, np.array([1.0, 5, 7]), "같은 A, b 만 바꿈")[같은 A, b 만 바꿈] 3 x 4, 랭크 2
피벗열 [1, 3] 자유열 [2, 4]
차원 행공간 2 | 영공간 2 | 열공간 2 | 좌영공간 1
모든 b 에 해가 있는가 : False (r=2, m=3)
해가 있다면 유일한가 : False (r=2, n=4)
열공간기저 (2개)
[ 1 2 ]
[ 2 6 ]
[ 3 8 ]
행공간기저 (2개)
[ 1 2 0 -2 ]
[ 0 0 1 2 ]
영공간기저 (2개)
[ -2 2 ]
[ 1 0 ]
[ 0 -2 ]
[ 0 1 ]
좌영공간기저 (1개)
[ -0.577 ]
[ -0.577 ]
[ 0.577 ]
Ax = b 의 해 : 없음
5. 네 가지 경우¶
L8에서 쓴 네 예제를 그대로 넣어 본다. 판정이 서술 파트의 표와 맞아야 한다.
경우들 = {
"r = m = n": np.array([[1.0, 2], [3, 4]]),
"r = n < m": np.array([[1.0, 0], [0, 1], [1, 1]]),
"r = m < n": np.array([[1.0, 0, 1], [0, 1, 1]]),
"r < m, r < n": np.array([[1.0, 2, 3], [2, 4, 6]]),
}
print(f"{'경우':>14}{'크기':>8}{'r':>4}{'차원 (행,영,열,좌영)':>22}{'해의 개수':>14}")
for 이름, M in 경우들.items():
정보 = 행렬해부(M)
m_, n_ = 정보["크기"]
판정 = ("언제나 1개" if 정보["모든b에해"] and 정보["해가유일"] else
"0개 또는 1개" if 정보["해가유일"] else
"언제나 무한히" if 정보["모든b에해"] else "0개 또는 무한히")
print(f"{이름:>14}{f'{m_}x{n_}':>8}{정보['랭크']:>4}"
f"{str(정보['차원']):>22}{판정:>14}") 경우 크기 r 차원 (행,영,열,좌영) 해의 개수
r = m = n 2x2 2 (2, 0, 2, 0) 언제나 1개
r = n < m 3x2 2 (2, 0, 2, 1) 0개 또는 1개
r = m < n 2x3 2 (2, 1, 2, 0) 언제나 무한히
r < m, r < n 2x3 1 (1, 2, 1, 1) 0개 또는 무한히
각 경우의 기약 사다리꼴 모양도 확인해 보자. 이면 가 없고, 이면 0인 행이 없다.
for 이름, M in 경우들.items():
Rref, 피벗 = 기약사다리꼴(M)
m_, n_ = M.shape
r_ = len(피벗)
print(f"{이름} : F 가 있는가 {r_ < n_}, 0 인 행이 있는가 {r_ < m_}")
print(show_matrix(Rref, " R"))r = m = n : F 가 있는가 False, 0 인 행이 있는가 False
R
[ 1 0 ]
[ 0 1 ]
r = n < m : F 가 있는가 False, 0 인 행이 있는가 True
R
[ 1 0 ]
[ 0 1 ]
[ 0 0 ]
r = m < n : F 가 있는가 True, 0 인 행이 있는가 False
R
[ 1 0 1 ]
[ 0 1 1 ]
r < m, r < n : F 가 있는가 True, 0 인 행이 있는가 True
R
[ 1 2 3 ]
[ 0 0 0 ]
6. 자주 하는 실수를 코드로 확인¶
서술 파트 5절의 다섯 가지를 하나씩 반증해 보자.
(1) 열공간의 기저를 에서 가져오면 안 된다.
A1 = np.array([[1.0, 2], [3, 6]])
R1, 피벗1 = 기약사다리꼴(A1)
print(show_matrix(A1, "A"))
print(show_matrix(R1, "R"))
print("A 의 피벗열 :", A1[:, 피벗1].ravel(), " -> C(A) 의 방향")
print("R 의 피벗열 :", R1[:, 피벗1].ravel(), " -> C(R) 의 방향")
print("두 공간이 같은가 :",
np.linalg.matrix_rank(np.column_stack([A1[:, 피벗1], R1[:, 피벗1]])) == 1)A
[ 1 2 ]
[ 3 6 ]
R
[ 1 2 ]
[ 0 0 ]
A 의 피벗열 : [1. 3.] -> C(A) 의 방향
R 의 피벗열 : [1. 0.] -> C(R) 의 방향
두 공간이 같은가 : False
(2) 이지 이 아니다.
P = np.array([[1.0, 2], [3, 5]])
Q = np.array([[2.0, 1], [1, 1]])
정답 = np.linalg.inv(Q) @ np.linalg.inv(P)
오답 = np.linalg.inv(P) @ np.linalg.inv(Q)
print("inv(PQ) == inv(Q) inv(P) :", np.allclose(np.linalg.inv(P @ Q), 정답))
print("inv(PQ) == inv(P) inv(Q) :", np.allclose(np.linalg.inv(P @ Q), 오답))inv(PQ) == inv(Q) inv(P) : True
inv(PQ) == inv(P) inv(Q) : False
(3) 특수해는 유일하지 않다. 자유 변수에 0을 넣는 것은 관례일 뿐이다.
정보 = 행렬해부(A, b)
xp = 정보["특수해"]
다른xp = xp + 2.0 * N[:, 0] - 1.0 * N[:, 1] # 영공간을 타고 옮긴 것
print("xp =", xp, " A xp =", A @ xp)
print("다른 xp =", 다른xp, " A xp =", A @ 다른xp)
print("둘 다 해인가 :", np.allclose(A @ xp, b) and np.allclose(A @ 다른xp, b))
print("차가 영공간에 있는가 :", np.allclose(A @ (다른xp - xp), 0))xp = [-2. 0. 1.5 0. ] A xp = [1. 5. 6.]
다른 xp = [-8. 2. 3.5 -1. ] A xp = [1. 5. 6.]
둘 다 해인가 : True
차가 영공간에 있는가 : True
(4) 랭크는 더해지지 않는다. 부등식이지 등식이 아니다.
단위 = np.eye(4)
print("가장 극단적인 예")
print(" rank(I) :", np.linalg.matrix_rank(단위))
print(" rank(-I) :", np.linalg.matrix_rank(-단위))
print(" rank(I + -I) :", np.linalg.matrix_rank(단위 - 단위), " (합이 0 행렬)")
print()
print("무작위로 여러 번")
print(f"{'rank P':>8}{'rank Q':>8}{'rank(P+Q)':>11}{'합 이하인가':>13}")
for _ in range(6):
P_ = rng.integers(-3, 4, (4, 4)).astype(float)
Q_ = rng.integers(-3, 4, (4, 4)).astype(float)
rP, rQ = np.linalg.matrix_rank(P_), np.linalg.matrix_rank(Q_)
rS = np.linalg.matrix_rank(P_ + Q_)
print(f"{rP:>8}{rQ:>8}{rS:>11}{str(rS <= rP + rQ):>13}")가장 극단적인 예
rank(I) : 4
rank(-I) : 4
rank(I + -I) : 0 (합이 0 행렬)
무작위로 여러 번
rank P rank Q rank(P+Q) 합 이하인가
4 4 4 True
4 4 4 True
3 4 4 True
4 3 4 True
4 4 4 True
4 4 4 True
(5) 해집합은 부분공간이 아니다. 두 해를 더하면 해가 아니다.
x1 = xp
x2 = xp + N[:, 0]
print("A x1 =", A @ x1)
print("A x2 =", A @ x2)
print("A(x1+x2) =", A @ (x1 + x2), " b =", b)
print("x1 + x2 도 해인가 :", np.allclose(A @ (x1 + x2), b))
print("영벡터는 해인가 :", np.allclose(A @ np.zeros(4), b))A x1 = [1. 5. 6.]
A x2 = [1. 5. 6.]
A(x1+x2) = [ 2. 10. 12.] b = [1. 5. 6.]
x1 + x2 도 해인가 : False
영벡터는 해인가 : False
7. 무작위 행렬로 검산¶
랭크를 정해 놓고 행렬을 만들면 네 관계가 언제나 성립하는지 확인할 수 있다.
print(f"{'m x n':>8}{'심은 r':>8}{'잰 r':>7}{'C R = A':>10}"
f"{'A N = 0':>10}{'차원 합':>9}{'랭크 대칭':>11}")
for _ in range(10):
m_ = int(rng.integers(2, 7))
n_ = int(rng.integers(2, 7))
k = int(rng.integers(0, min(m_, n_) + 1))
M = (rng.integers(-3, 4, (m_, k)) @ rng.integers(-3, 4, (k, n_))).astype(float)
정보 = 행렬해부(M)
r_ = 정보["랭크"]
C_, R_ = 정보["열공간기저"], 정보["행공간기저"]
N_ = 정보["영공간기저"]
검사 = (
np.allclose(C_ @ R_, M),
np.allclose(M @ N_, 0),
정보["차원"][0] + 정보["차원"][1] == n_ and 정보["차원"][2] + 정보["차원"][3] == m_,
np.linalg.matrix_rank(M) == np.linalg.matrix_rank(M.T) == r_,
)
print(f"{f'{m_} x {n_}':>8}{k:>8}{r_:>7}"
f"{str(검사[0]):>10}{str(검사[1]):>10}{str(검사[2]):>9}{str(검사[3]):>11}") m x n 심은 r 잰 r C R = A A N = 0 차원 합 랭크 대칭
6 x 5 1 1 True True True True
5 x 5 4 4 True True True True
4 x 4 1 1 True True True True
4 x 3 2 2 True True True True
5 x 6 2 2 True True True True
4 x 6 2 2 True True True True
6 x 5 2 2 True True True True
4 x 2 0 0 True True True True
3 x 4 1 1 True True True True
4 x 2 1 1 True True True True
심은 r 과 잰 r 이 다를 때가 있다. 무작위 정수 두 조각을 곱하다 우연히 열이 겹치면
랭크가 심은 것보다 작아지기 때문이다. 어느 쪽이든 네 검사는 전부 통과한다.
마치며...¶
| 서술 파트의 내용 | 이 노트북의 코드 |
|---|---|
| 피벗이 세 가지를 동시에 센다 | matrix_rank(A), matrix_rank(A.T), len(피벗) |
랭크분해(A) | |
| 가 열공간의 기저 | column_stack([A, C]) 의 랭크가 그대로 |
| 이 행공간의 기저 | vstack([A, R]) 의 랭크가 그대로 |
| 행랭크 열랭크 | C.shape[1] == R.shape[0] |
| 랭크 1 벽돌 개 | np.outer 의 합 |
| 영공간 행렬 | 영공간행렬(A) |
| 해부 절차 | 행렬해부(A, b) |
더 해 볼 것¶
행렬해부에 좌영공간의 기저를null_space(A.T)대신 소거로 구하도록 고쳐 보자. 를 소거하면 0이 된 행에 대응하는 오른쪽 조각이 답이다.랭크분해로 얻은 와 을 곱해 를 복원할 때 소수점 오차가 얼마나 나는지 큰 행렬로 재 보자.7절의 검사에 “ 의 열이 독립인가”, “ 의 행이 독립인가” 를 추가해 보자.
랭크 1 벽돌의 순서를 바꿔서 더해도 가 나오는가? 벽돌 하나를 빼면 랭크가 얼마가 되는가?
다음 강의에서 내적이 등장한다. 두 벡터가 직각인지 물을 수 있게 되고, L10에서 그려 놓고 설명하지 못했던 직각의 이유를 확인하게 된다.