← --library
이과 · 02이과

선형대수학 및 텐서 해석

Linear Algebra & Tensors

단계1단계2단계3단계4단계5

5단계: 수치 계산의 엔진실 — 행렬을 쪼개고, 반복하고, 병렬화하라

이론적 기초: 컴퓨터와 수학의 간극

1단계에서 가우스 소거법을 배웠을 때, 우리는 종이 위에서 완벽하게 연산을 수행했다. 그런데 진짜 세계는 다르다. 기상예보 시스템은 수백만 개의 방정식을 매 시간 풀어야 하고, ChatGPT의 한 번의 추론에는 수십억 개의 행렬 연산이 초당 수천 번 실행된다. 이때 종이 위의 수학과 컴퓨터 안의 수학 사이에는 무너뜨려야 할 벽이 하나 있다. **수치 안정성(numerical stability)**이라는 벽이다. 이 단계는 선형대수학의 "이론"이 실제 기계 위에서 살아 돌아가도록 만드는 엔지니어링 레이어다.

먼저 컴퓨터가 숫자를 다루는 방식을 이해해야 한다. 1/3을 계산기에 입력하면 0.333333...이 나오다 어느 순간 끊긴다. 컴퓨터는 실수를 무한 정밀도로 저장할 수 없기 때문에 부동소수점(floating-point) 표현이라는 방식을 쓴다. 국제 표준 IEEE 754에 따르면 64비트 double 타입은 약 15-17자리의 십진수 정밀도를 가진다. 이 말은 너무 크거나 작은 두 수를 더하면 작은 수가 아예 사라지는 현상이 생긴다는 뜻이다. 10^16 + 1을 64비트로 계산하면 1이 반올림 오류로 사라진다. 이것이 **반올림 오류(round-off error)**다.

[노트 기록] 기계 엡실론(machine epsilon, ε_m): 컴퓨터가 1.0과 구분할 수 있는 가장 작은 수. Python에서 import sys; sys.float_info.epsilon을 실행하면 약 2.22×10⁻¹⁶이 나온다. 이보다 작은 상대적 차이는 컴퓨터가 구별하지 못한다.

3단계에서 배운 **조건수(condition number) κ(A)**를 기억하는가? 조건수가 크다는 것은 입력의 작은 변화가 출력의 큰 변화를 낳는다는 뜻이었다. 수치 계산에서 이 의미는 훨씬 날카롭다. κ(A) ≈ 10^k라면 Ax = b를 풀 때 대략 k자리의 유효 숫자가 손실된다. κ = 10¹²인 시스템을 64비트(약 16자리 정밀도)로 풀면 결과에 신뢰할 수 있는 자리는 겨우 4자리뿐이다. 이 원리를 마음속에 새겨두어라. 이후 분해와 반복법을 선택할 때마다 이것이 기준이 된다. 그리고 1단계의 가우스 소거법은 n×n 행렬을 푸는 데 O(n³)의 연산이 필요하다는 것도 기억하는가? n = 10,000이면 10¹²번이다. 현대의 기상 시뮬레이션은 격자점이 100만 개를 넘는다. O(n³)으로 풀면 우주가 끝날 때까지 기다려야 한다. 이것이 더 영리한 방법이 필요한 이유다.


본론 1: LU 분해 — 가우스 소거를 한 번만 하면 되도록

1단계 가우스 소거법의 본질을 다시 떠올려보자. Ax = b를 풀 때 소거 연산을 통해 A를 상삼각 행렬(upper triangular)로 만들었다. **LU 분해(LU decomposition)**는 그 소거 과정을 두 행렬에 저장해버리는 아이디어다. A = LU, 여기서 L은 대각선이 전부 1인 하삼각 행렬(lower triangular), U는 상삼각 행렬이다. "LU = 가우스 소거를 L에 저장한 것"이라는 눈치밥 스킬의 의미가 바로 이것이다.

왜 이게 유용한가? 스스로 생각해보아라. Ax₁ = b₁, Ax₂ = b₂, Ax₃ = b₃처럼 같은 A로 여러 번 시스템을 풀어야 한다면? 가우스 소거를 매번 하면 O(n³)을 매번 지불해야 한다. 하지만 LU를 한 번 분해해두면, 이후 각 새로운 b에 대해 Ly = b(전진 대입)와 Ux = y(후진 대입), 두 삼각 시스템만 풀면 된다. 삼각 시스템은 O(n²)에 풀린다. 투자가 한 번, 회수가 무한정인 구조다.

[노트 기록] LU 분해 풀이 흐름: Ax = b → A = LU → Ly = b (y를 전진 대입으로 구함) → Ux = y (x를 후진 대입으로 구함)

실제로 손으로 해보자. 행렬 A = [[2, 1, 1], [4, 3, 3], [8, 7, 9]]에서 가우스 소거를 수행하면: 2행에서 (4/2)=2배의 1행을 빼고, 3행에서 (8/2)=4배의 1행을 뺀다. 이때 사용된 배수 2와 4가 바로 L 행렬의 아랫부분으로 들어간다. 계속 소거하면 U = [[2, 1, 1], [0, 1, 1], [0, 0, 2]], L = [[1, 0, 0], [2, 1, 0], [4, 3, 1]]이 된다. L·U를 직접 곱해서 원래 A가 나오는지 확인해보아라.

그런데 수치적으로 큰 문제가 있다. A의 (1,1) 원소, 즉 피벗(pivot)이 0이거나 매우 작으면 어떻게 될까? 0으로 나누거나 매우 작은 수로 나누면 오류가 폭발한다. 이를 해결하는 것이 **부분 피벗팅(partial pivoting)**이다. 각 단계에서 현재 열의 절댓값 최대 원소를 피벗으로 삼도록 행을 교환한다. 행렬로 표현하면 PA = LU이며, P는 순열 행렬(permutation matrix)이다. numpy의 linalg.lu()도 내부적으로 P, L, U 세 개를 반환한다. 실제 세계의 LU는 항상 PA = LU 형태임을 기억하라.


본론 2: QR 분해 — 직교성의 힘

2단계에서 배운 그람-슈미트 직교화를 기억하는가? 선형 독립인 벡터들을 받아서 직교 정규 기저를 만드는 과정이었다. 이제 그것을 행렬 전체에 적용하면 바로 **QR 분해(QR decomposition)**가 된다. A = QR, 여기서 Q는 직교 행렬(Q^T Q = I), R은 상삼각 행렬이다. LU와 QR은 모두 행렬을 분해하지만 목적이 다르다. 언제 뭘 쓰는가?

LU는 정방 행렬의 Ax = b 풀기에 최적화되어 있다. QR은 과결정 시스템(overdetermined system), 즉 방정식이 미지수보다 많은 경우의 최소제곱 문제에 빛을 발한다. 2단계에서 배운 정규 방정식 A^T Ax = A^T b를 기억하는가? 이때 A^T A의 조건수는 A의 조건수의 제곱이 된다. A의 조건수가 100이면 A^T A의 조건수는 10,000이 된다는 뜻이다. QR 분해를 쓰면 A 자체의 조건수로 문제를 풀 수 있어 수치적으로 훨씬 안전하다. 3단계의 조건수 지식이 여기서 결정적인 역할을 한다.

[노트 기록] LU vs QR 사용 기준: 정방 시스템 Ax = b → LU (부분 피벗팅 포함) / 과결정 최소제곱 min‖Ax - b‖₂ → QR / 고유값 계산 알고리즘(QR 반복법) → QR / 수치 안정성이 매우 중요한 경우 → QR. 요약: 정방이면 LU, 직교성과 안정성이 필요하면 QR.

그람-슈미트로 QR을 구하는 방법은 2단계에서 배운 그대로다. 단, 실제 소프트웨어는 수치적으로 더 안정적인 **하우스홀더 반사(Householder reflection)**를 쓴다. 이 방법은 벡터를 초평면에 반사시켜 특정 원소들을 0으로 만드는 방식으로, 그람-슈미트보다 반올림 오류에 강하다. 개념적으로, "거울에 반사시켜서 벡터를 좌표축과 정렬한다"고 생각하면 된다. numpy의 linalg.qr()은 내부적으로 하우스홀더 반사를 사용한다.


본론 3: 반복법 — 대규모 시스템의 유일한 선택

직접법(Direct method)인 LU와 QR은 n×n 행렬에 O(n³) 연산과 O(n²) 메모리를 요구한다. 현대의 큰 시스템을 생각해보자. 3D 유체 시뮬레이션의 격자가 1000×1000×1000이면 n = 10⁹이다. 이를 LU로 풀면 10²⁷번의 연산이 필요하다. 슈퍼컴퓨터조차 불가능하다. 그런데 다행히, 이런 시스템의 행렬은 대부분 **희소(sparse)**하다. 각 격자점은 인접한 몇 개의 점하고만 연결되어 있기 때문에 대부분의 원소가 0이다. **반복법(iterative method)**은 이 희소성을 보존하면서 근사 해를 점진적으로 개선한다.

가장 직관적인 반복법인 **야코비법(Jacobi method)**부터 시작하자. Ax = b에서 i번째 방정식을 x_i에 대해 풀고, 나머지 변수들은 현재 추정값을 사용한다. 이를 반복하면 x가 참값으로 수렴한다(조건이 맞다면). 속도가 느리지만 개념이 명확하다. 자전거 타는 법을 처음 배울 때 보조 바퀴를 다는 것과 비슷하다.

더 강력한 것이 **켤레 기울기법(Conjugate Gradient, CG)**이다. A가 대칭 양정치(Symmetric Positive Definite, SPD) 행렬, 즉 A = A^T이고 모든 고유값이 양수인 경우에 적용된다. 2단계에서 배운 "대칭 행렬은 실수 고유값을 가진다"는 사실과 바로 연결된다. CG의 핵심 아이디어는 Ax = b의 해가 이차 함수 f(x) = (1/2)x^T Ax - b^T x의 최솟값임을 이용하는 것이다. 최솟값을 찾기 위해 서로 켤레인(A-conjugate) 탐색 방향들을 순서대로 사용한다. 켤레란 p_i^T A p_j = 0 (i ≠ j)을 의미하며, 3단계에서 특이벡터가 직교했던 것과 개념적으로 연결된다.

[노트 기록] CG 수렴 속도: ‖e_k‖_A ≤ 2·((√κ - 1)/(√κ + 1))^k ‖e_0‖_A. 여기서 κ = λ_max/λ_min (조건수). κ가 작을수록 빠르게 수렴. 즉 수렴 속도도 결국 조건수에 의존한다!

A가 대칭이 아닌 일반 행렬에는 **GMRES(Generalized Minimal RESidual)**를 쓴다. GMRES는 각 반복에서 크릴로프 부분공간(Krylov subspace) K_k(A, r₀) = span{r₀, Ar₀, A²r₀, ..., A^{k-1}r₀} 위에서 잔차(residual) ‖b - Ax‖를 최소화하는 해를 구한다. 크릴로프 부분공간이란 초기 잔차 r₀에 A를 반복 적용하여 만든 공간이며, 아른올디 반복(Arnoldi iteration)이라는 과정으로 구축된다. 이 과정 내부에서 QR 분해와 그람-슈미트가 사용된다. 단계들이 하나의 거대한 연결망을 이루고 있다는 것을 느끼고 있는가? **전처리(Preconditioning)**는 반복법의 성능을 드라마틱하게 향상시키는 기법이다. Ax = b 대신 M⁻¹Ax = M⁻¹b를 푸는 것인데, M ≈ A이지만 M⁻¹을 적용하기 쉬운 행렬을 선택한다. 잘 선택된 M은 κ(M⁻¹A) ≪ κ(A)가 되어 수렴이 훨씬 빨라진다.


본론 4: 희소 행렬과 압축 저장

물리 시뮬레이션, 그래프 분석, 유한 원소법에서 등장하는 행렬들은 대부분 **희소 행렬(sparse matrix)**이다. n = 10⁶이고 0이 아닌 원소가 10⁷개뿐이라면, n² = 10¹²개를 모두 저장하는 것은 메모리 낭비이자 불가능에 가깝다. 가장 널리 쓰이는 형식은 **CSR(Compressed Sparse Row)**이다. 세 개의 배열로 행렬을 표현한다: val은 0이 아닌 원소들의 값, col_ind는 각 원소의 열 인덱스, row_ptr은 각 행이 val에서 시작하는 인덱스(크기 n+1)다.

[노트 기록] CSR 예시: A = [[4, 0, 2], [0, 3, 0], [1, 0, 5]] → val = [4, 2, 3, 1, 5], col_ind = [0, 2, 1, 0, 2], row_ptr = [0, 2, 3, 5]. row_ptr[i]부터 row_ptr[i+1]-1까지가 i행의 원소들.

이 표현 방식이 왜 반복법과 잘 맞는가? 반복법의 핵심 연산은 행렬-벡터 곱 Ax다. CSR 형식에서 이 연산은 0이 아닌 원소의 수 nnz에 비례하는 O(nnz) 시간만 걸린다. 반면 직접법(LU)은 가우스 소거 과정에서 원래 0이었던 자리가 0이 아닌 값으로 채워지는 필인(fill-in) 문제가 생겨, 희소성이 파괴될 수 있다. 이것이 "희소 행렬이면 반복법"이라는 눈치밥 스킬의 이유다. scipy에서는 scipy.sparse가, PyTorch에서는 torch.sparse가 이를 지원한다.


본론 5: 딥러닝 텐서 연산 — einsum과 브로드캐스팅

4단계에서 아인슈타인 합 규칙을 배웠다. 같은 인덱스가 위아래(반변, 공변)로 나타나면 합산한다는 규칙이었다. **einsum**은 이것의 컴퓨터 구현이다. torch.einsum('ij,jk->ik', A, B)는 A의 i번째 행과 B의 k번째 열의 j에 대한 합, 즉 행렬 곱셈 C_{ik} = Σ_j A_{ij} B_{jk}를 표현한다. 화살표 왼쪽에는 입력 인덱스들, 오른쪽에는 출력 인덱스를 적는다. 출력에 나타나지 않는 인덱스는 자동으로 합산(수축, contraction)된다.

[노트 기록] einsum 핵심 패턴:

  • 행렬 곱셈: 'ij,jk->ik'
  • 내적(스칼라): 'i,i->' (오른쪽 비워두면 스칼라 출력)
  • 외적: 'i,j->ij'
  • 배치 행렬 곱셈: 'bij,bjk->bik' (b는 배치 차원, 합산 안 함)
  • 트레이스: 'ii->'
  • 전치: 'ij->ji'
  • 대각선 추출: 'ii->i'

**브로드캐스팅(broadcasting)**은 모양이 다른 텐서들을 자동으로 맞춰 연산하는 규칙이다. 핵심 규칙: 두 텐서의 차원을 오른쪽부터 정렬했을 때, 각 차원이 (1) 같거나 (2) 둘 중 하나가 1이어야 한다. 크기 1인 차원은 연산 시점에 자동으로 "복제"된다. 실제로 메모리를 복사하지 않고 인덱싱만 조정하기 때문에 효율적이다. (100, 1, 256) + (1, 50, 256) = (100, 50, 256). 트랜스포머의 어텐션 마스크 적용, 배치 정규화, 학습률 적용 등 딥러닝의 거의 모든 곳에서 브로드캐스팅이 암묵적으로 사용된다.


본론 6: 행렬곱 순서 최적화 — 결합법칙의 돈 값

행렬 곱셈은 결합 법칙이 성립한다. (AB)C = A(BC). 하지만 연산량은 순서에 따라 천문학적으로 달라진다. 구체적인 예를 보자. 세 행렬 A₁(10×100), A₂(100×5), A₃(5×50)의 곱을 구해야 한다. 순서 1, (A₁A₂)A₃: A₁A₂의 비용은 10×100×5 = 5,000회, 결과는 10×5 행렬. 이것과 A₃의 곱 비용은 10×5×50 = 2,500회. 총 7,500회다. 순서 2, A₁(A₂A₃): A₂A₃의 비용은 100×5×50 = 25,000회, 결과는 100×50 행렬. 이것과 A₁의 곱 비용은 10×100×50 = 50,000회. 총 75,000회다. 같은 결과를 구하는데 연산량이 10배 차이 난다.

[노트 기록] 행렬 곱 비용 공식: A(m×k)와 B(k×n)의 곱 AB는 m × k × n 회의 스칼라 곱셈. 중간 차원 k의 크기가 핵심. 작은 행렬을 먼저 만들어서 이후 곱셈의 차원을 줄이는 것이 전략이다.

딥러닝에서 수십 개의 행렬이 연속으로 곱해지는 경우, 순서를 잘못 선택하면 연산 시간이 100배 이상 차이 날 수 있다. 이 연쇄 행렬 곱셈(Chain Matrix Multiplication) 문제는 동적 프로그래밍(Dynamic Programming)으로 O(n³) 시간에 최적 순서를 찾을 수 있다. PyTorch의 torch.linalg.multi_dot()은 내부적으로 이 최적화를 수행한다.


본론 7: GPU 병렬 행렬 연산

CPU는 복잡한 명령을 빠른 소수의 코어로 처리한다. GPU는 단순한 연산을 수천 개의 코어로 동시에 처리한다. 행렬 곱셈 C = AB에서 C의 각 원소 c_{ij} = Σ_k a_{ik} b_{kj}는 다른 원소들과 독립적이다. 모든 c_{ij}를 동시에 병렬로 계산할 수 있다는 뜻이다. 이것이 GPU가 행렬 연산에 완벽하게 들어맞는 이유다.

실제 GPU는 행렬을 작은 블록(tile)으로 나누는 타일링(tiling) 기법으로 처리한다. 각 블록을 GPU의 고속 공유 메모리(shared memory)에 올려두고 계산한 뒤 결과를 전역 메모리에 쓴다. 메모리 대역폭이 종종 GPU 성능의 병목이다. 불규칙한 메모리 접근 패턴을 가진 희소 행렬은 밀집 행렬보다 GPU 효율이 낮을 수 있는데, 이를 메모리 접근 비국소성 문제라고 한다. 현대 GPU에는 행렬 연산에 특화된 **텐서 코어(Tensor Core)**도 있어 fp16, bf16 형식의 특정 행렬 곱셈을 더욱 빠르게 수행한다. PyTorch와 TensorFlow는 cuBLAS, cuDNN 같은 NVIDIA 라이브러리를 내부에서 호출해 이 가속을 자동으로 이용한다.


프로젝트: GPU 텐서 연산 최적화 — 직접 도전하라

이제까지 배운 내용을 손과 머리로 직접 확인할 시간이다. 공식을 적용하는 것이 아니라 왜 이 방법이 더 나은지를 스스로 추론하는 것에 초점을 맞추어라. 정답은 없다. 고민하는 시간이 학습이다.


문제 1: LU 분해 손 계산 (약 10분)

아래 행렬 A를 PA = LU 형태로, 부분 피벗팅을 적용하여 분해하라.

A = [[ 1,  2, -1],
     [ 2,  1,  3],
     [-1,  3,  2]]

(1) 첫 번째 소거 단계에서 어떤 행을 피벗 행으로 선택해야 하는가? 왜 수치적으로 그 행이 유리한가?

(2) L과 U를 손으로 구하라. 검증: L·U = P·A가 성립하는지 직접 곱해서 확인하라.

(3) 이 LU 분해를 이용하여 b = [4, 7, 1]^T에 대해 Ax = b를 전진/후진 대입으로 풀어라.

(4) 이 LU를 한 번 구해두었을 때, b₁ = [1,0,0]^T, b₂ = [0,1,0]^T, b₃ = [0,0,1]^T를 각각 풀기 위한 총 연산 횟수(LU 분해 포함 vs 미포함)를 비교하면 어떻게 되는가? 이 세 풀이의 결과를 나란히 놓으면 어떤 행렬이 완성되는가?


문제 2: QR 분해와 최소제곱 (약 10분)

아래는 시간 t에서 측정된 신호 y다 (노이즈 포함).

t 0 1 2 3 4
y 1.2 2.9 5.1 7.0 9.3

y ≈ at + b 라는 선형 모델로 피팅하고자 한다.

(1) 이 문제를 Ax ≈ b 형태의 행렬 방정식으로 표현하라. A의 크기(shape)는 몇×몇인가? 미지수 벡터 x의 성분은 무엇인가?

(2) 이 시스템은 정확한 해가 존재하는가? 그 이유를 rank 관점에서 설명하라.

(3) 정규 방정식 A^T Ax = A^T b를 직접 구성하고 풀어 a와 b의 값을 구하라.

(4) A의 두 열이 거의 평행에 가깝다면(예: 한 열이 다른 열의 1.001배), κ(A)와 κ(A^T A)의 관계는 어떻게 되는가? 이 상황에서 QR 분해가 정규 방정식보다 왜 더 안전한지를 조건수의 언어로 설명하라.


문제 3: 희소 행렬과 CSR 변환 (약 5분)

아래 5×5 행렬이 있다.

A = [[4, 0, 0, 2, 0],
     [0, 3, 0, 0, 1],
     [0, 0, 5, 0, 0],
     [2, 0, 0, 6, 0],
     [0, 1, 0, 0, 7]]

(1) 이 행렬을 CSR 형식(val, col_ind, row_ptr)으로 표현하라.

(2) 밀집 형식과 CSR 형식 각각의 저장 원소 수(val + col_ind + row_ptr 포함)를 비교하라.

(3) n×n 크기이고 각 행에 평균 k개의 비영(non-zero) 원소가 있을 때, 밀집 저장과 CSR 저장의 메모리 비율 식을 유도하라. n = 10⁶, k = 5인 경우 몇 배 차이가 나는가?

(4) 이 행렬에 LU 분해를 적용했을 때 fill-in이 발생할 가능성이 있는 위치를 직관적으로 예측하라. (힌트: 첫 번째 소거 단계 이후 (3,1)과 (4,3) 위치를 살펴보아라.)


문제 4: einsum으로 신경망 연산 표현 (약 8분)

트랜스포머 셀프 어텐션에서 배치 b=2, 시퀀스 길이 n=4, 헤드 수 h=2, 헤드별 차원 d=8이다.

(1) 아래 einsum 표기가 어떤 수학적 연산에 해당하는지, 그리고 출력 텐서의 shape을 각각 구하라.

  • 'bnd,bmd->bnm' (Q와 K의 어텐션 스코어)
  • 'bhqd,bhkd->bhqk' (멀티헤드 어텐션 스코어)
  • 'bhqk,bhkd->bhqd' (어텐션 가중합)

(2) torch.einsum('ij,jk,kl->il', A, B, C)를 해석하고, 내부적으로 어떤 순서로 연산을 수행하는지 einsum이 어떻게 결정하는지 논하라. (A @ B) @ CA @ (B @ C)와 같은 결과인가?

(3) 브로드캐스팅 규칙에 따라 아래 연산의 결과 shape을 구하고, 각각이 딥러닝에서 어떤 상황에 쓰일 법한지 추측하라.

  • (2, 3, 1) + (1, 3, 4)
  • (batch, seq, 1) * (1, 1, hidden_dim)
  • (heads, seq, seq) + (1, 1, seq) — 이것은 어텐션 마스크를 추가할 때와 어떤 관련이 있는가?

문제 5: 행렬곱 순서 최적화 (약 7분)

딥러닝 모델에서 네 행렬의 곱을 계산해야 한다.

  • A: 1 × 1000
  • B: 1000 × 500
  • C: 500 × 100
  • D: 100 × 1

(1) 괄호를 다르게 묶는 모든 가능한 순서를 나열하라 (행렬 순서 A,B,C,D는 고정).

(2) 각 순서의 스칼라 곱셈 횟수를 계산하라.

(3) 가장 효율적인 순서와 가장 비효율적인 순서의 연산량 비율을 구하라.

(4) 이 연산을 딥러닝 훈련 중 하루에 10억 번 반복한다고 가정하면, 최적 순서와 최악 순서의 차이는 전력 소비와 학습 시간 측면에서 실용적으로 어떤 의미를 갖는가? (정량적 수치 계산이 아닌, 개념적으로 서술하라.)


보너스 문제: 수치 안정성 코드 진단 (약 5분)

아래 코드의 수치 안정성 문제를 찾고 수정 방향을 제시하라.

def solve_least_squares(A, b):
    ATA = A.T @ A
    ATb = A.T @ b
    x = np.linalg.inv(ATA) @ ATb  # 역행렬 명시적 계산
    return x

(1) np.linalg.inv(ATA)를 쓰는 것과 np.linalg.solve(ATA, ATb)를 쓰는 것은 수치적으로 어떻게 다른가? (힌트: solve는 내부적으로 LU를 사용한다. inv의 계산 과정은 무엇인가?)

(2) A의 두 열이 거의 선형 종속이라면 κ(ATA)는 κ(A)에 비해 어떻게 되는가? 이때 위 코드의 결과에 어떤 영향이 생기는가?

(3) 이 함수를 수치적으로 더 안전하게 만들기 위해 어떤 분해를 써야 하는가? 왜 그 방법이 더 나은지 이 단계에서 배운 내용을 근거로 설명하라.


자기 평가 기준

문제를 풀고 나서 아래 기준으로 스스로 점검해보아라. 분해 수행 능력(20점): 문제 1에서 P, L, U를 올바르게 구하고 두 단계 대입법으로 Ax = b를 풀었는가? 피벗팅의 이유를 자신의 말로 설명할 수 있는가? 반복법 및 수치 안정성(20점): 문제 2(4)와 보너스 문제에서 조건수가 분해 방법 선택에 어떻게 영향을 미치는지 논리적으로 연결할 수 있는가? 텐서 연산(30점): 문제 4에서 einsum 표기법을 정확히 해석하고 shape을 계산했으며, 브로드캐스팅 규칙을 실제 딥러닝 맥락과 연결할 수 있는가? 최적화(30점): 문제 5에서 최적 순서를 수치로 확인하고, 그 의미를 실용적인 관점에서 자신의 언어로 설명할 수 있는가?

← 단계 4