수학

MATH / 중급 7번

직교와 사영: 한 벡터를 다른 벡터로 설명하고 남은 것

직교를 내적 0으로 정의하고 정규직교기저가 왜 편한지를 봅니다. 한 벡터를 다른 벡터·부분공간 위로 사영하는 공식을 잔차의 직교 조건에서 유도하고, 사영 행렬 P = A(AᵀA)⁻¹Aᵀ와 최소제곱의 기하를 손으로 확인합니다. 직교 초기화가 강제하는 것과, 멀티헤드가 직교와 무엇이 다른지도 짚습니다.

PALDYN Team46 MIN READ

RNN이나 깊은 선형층을 쓰는 코드에서 이런 줄을 자주 봅니다.

nn.init.orthogonal_(self.weight)

"직교 초기화"라고 부르지만 무엇을 직교시킨다는 것인지, 그렇게 하면 무엇이 보장되는지는 대개 설명 없이 지나갑니다. 비슷하게 멀티헤드 어텐션을 설명하는 글은 "각 헤드가 서로 다른 것을 본다"고 적는데, 그 말이 직교와 같은 뜻인지도 흐릿합니다.

이 글은 직교를 정의부터 세우고, 거기서 사영을 유도한 뒤 그 둘로 두 물음에 답합니다. 사영은 한 벡터를 다른 벡터로 설명할 수 있는 만큼 설명하고 남은 것을 떼어 내는 조작이고, 최소제곱부터 직교 초기화까지가 전부 그 위에 있습니다.

직교의 정의

지난 글에서 a⋅b=∥a∥∥b∥cos⁡θ\mathbf{a}\cdot\mathbf{b} = \|\mathbf{a}\|\|\mathbf{b}\|\cos\theta 를 얻었습니다. 두 벡터가 0이 아니면 내적이 0이 되는 것은 cos⁡θ=0\cos\theta = 0, 즉 θ=90∘\theta = 90^\circ 일 때뿐입니다.

정의. a⋅b=0\mathbf{a}\cdot\mathbf{b} = 0 이면 두 벡터가 직교한다고 한다.

직교는 각도를 재지 않고도 확인됩니다 — 곱해서 더한 값이 0인지만 보면 됩니다. 이것이 고차원에서 각도를 다루는 유일하게 실용적인 방법입니다.

직교와 짝을 이루는 성질이 하나 더 있습니다. 지난 글의 전개에서 부호만 바꾸면

∥a+b∥2=∥a∥2+2 a⋅b+∥b∥2\|\mathbf{a}+\mathbf{b}\|^2 = \|\mathbf{a}\|^2 + 2\,\mathbf{a}\cdot\mathbf{b} + \|\mathbf{b}\|^2

이므로 a⋅b=0\mathbf{a}\cdot\mathbf{b} = 0 인 것과 ∥a+b∥2=∥a∥2+∥b∥2\|\mathbf{a}+\mathbf{b}\|^2 = \|\mathbf{a}\|^2+\|\mathbf{b}\|^2 인 것이 서로 같은 말입니다. 피타고라스 정리가 성립하는 조건이 곧 직교의 정의입니다. 이 동치를 뒤에서 최소제곱을 증명할 때 그대로 씁니다.

정규직교기저

서로 직교하면서 각자의 노름이 1인 벡터들의 모음을 정규직교기저라고 합니다. q1,…,qn\mathbf{q}_1, \ldots, \mathbf{q}_n 이 그런 모음이면

qi⋅qj={1(i=j)0(i≠j)\mathbf{q}_i \cdot \mathbf{q}_j = \begin{cases} 1 & (i = j) \\ 0 & (i \ne j) \end{cases}

이 조건이 좋은 이유는 좌표를 구하는 일이 내적 한 번으로 끝나기 때문입니다. 임의의 x\mathbf{x} 를 x=∑iciqi\mathbf{x} = \sum_i c_i \mathbf{q}_i 로 적었다고 하고 양변에 qj\mathbf{q}_j 를 내적하면, 오른쪽에서 i≠ji \ne j 인 항이 전부 0이 되어

cj=x⋅qjc_j = \mathbf{x}\cdot\mathbf{q}_j

보통의 기저였다면 연립방정식을 풀어야 할 일이 곱셈 한 번이 됩니다.

이 벡터들을 열로 세운 행렬 QQ 는 Q⊤Q=IQ^\top Q = I 를 만족합니다. 그러면 노름이 보존됩니다.

∥Qx∥2=(Qx)⊤(Qx)=x⊤Q⊤Q x=x⊤x=∥x∥2\|Q\mathbf{x}\|^2 = (Q\mathbf{x})^\top(Q\mathbf{x}) = \mathbf{x}^\top Q^\top Q\,\mathbf{x} = \mathbf{x}^\top\mathbf{x} = \|\mathbf{x}\|^2

직교행렬을 곱하는 것은 회전(또는 뒤집기)일 뿐 크기를 바꾸지 않습니다. 첫머리의 orthogonal_ 이 강제하는 것이 정확히 이 한 줄이고, 그 뜻은 아래에서 다시 봅니다.

영벡터라는 예외

정의를 글자 그대로 읽으면 영벡터는 자기 자신을 포함해 모든 벡터와 직교합니다. 0⋅a=0\mathbf{0}\cdot\mathbf{a} = 0 이 언제나 성립하기 때문입니다. 직각이라는 그림과는 어울리지 않는 말이라 처음에는 이상하게 들립니다.

그런데 이 관례가 정의를 깨지는 않습니다. 직교를 각도가 아니라 내적으로 정의해 둔 덕분입니다. 각도로 정의했다면 길이가 0인 벡터에는 방향이 없어 cos⁡θ\cos\theta 자체가 정의되지 않고, "영벡터는 직교하는가"라는 물음에 답할 수가 없습니다. 내적으로 정의하면 그 물음이 계산 한 줄로 끝납니다.

실무에서 이 관례가 도움이 되는 자리도 있습니다. 뒤에 나올 잔차는 사영이 완벽하게 맞아떨어질 때 영벡터가 되는데, 그때도 "잔차가 열공간과 직교한다"는 문장이 그대로 참으로 남습니다. 예외를 따로 적을 필요가 없습니다.

직교와 일차독립

직교는 생각보다 강한 조건입니다. 0이 아닌 벡터들이 서로 직교하면, 그것만으로 이미 일차독립입니다. 여기서 일차독립이란 그중 어느 하나도 나머지를 늘이고 더해 만들어 낼 수 없다는 뜻이고, 같은 말로 c1v1+⋯+ckvk=0c_1\mathbf{v}_1 + \cdots + c_k\mathbf{v}_k = \mathbf{0} 을 만족하는 계수가 전부 0뿐이라는 뜻입니다.

증명이 한 줄입니다. 위 식의 양변에 vj\mathbf{v}_j 를 내적하면 i≠ji \ne j 인 항이 직교라서 전부 사라지고

cj ∥vj∥2=0c_j\,\|\mathbf{v}_j\|^2 = 0

만 남습니다. vj≠0\mathbf{v}_j \ne \mathbf{0} 이므로 ∥vj∥2≠0\|\mathbf{v}_j\|^2 \ne 0 이고, 따라서 cj=0c_j = 0 입니다. jj 를 아무거나 골라도 같으니 계수가 전부 0입니다.

일차독립을 확인하려면 보통은 소거를 해 봐야 하는데, 직교만 확인되면 그 일이 필요 없습니다. 내적 몇 번으로 끝나는 검사가 소거를 대신하는 것이 정규직교기저를 선호하는 또 하나의 이유입니다.

고차원에서 벌어지는 일

차원이 커지면 직교가 특별한 사건이 아니게 됩니다. 평면에서 무작위로 두 방향을 고르면 사잇각이 고르게 퍼져 직각은 드문 일인데, 차원이 높아지면 거의 모든 쌍이 직각 근처에 몰립니다.

dd 차원에서 무작위로 고른 두 단위벡터의 코사인은 평균이 0이고 흩어진 정도가 1/d1/\sqrt{d} 규모입니다. 수로 보면 이렇습니다.

차원 dd 코사인의 전형적 크기 그때의 사잇각
2 0.707 45°
10 0.316 71.6°
100 0.100 84.3°
1,000 0.032 88.2°
10,000 0.010 89.4°

차원이 커질수록 무작위 두 벡터의 코사인이 0으로 모인다

임베딩 차원이 흔히 수백에서 수천이라는 것을 생각하면, 그 공간에서 아무렇게나 고른 두 벡터는 사실상 직교합니다. 그래서 "두 벡터가 직교한다"는 관찰 자체는 고차원에서 정보가 거의 없고, 의미가 있는 것은 언제나 그 기준선보다 얼마나 큰 코사인이 나왔는가입니다. 이 사실은 다섯째 절에서 코드로 직접 재 봅니다.

한 벡터 위로의 사영

a\mathbf{a} 를 b\mathbf{b} 의 방향으로만 표현하려 합니다. 즉 tbt\mathbf{b} 꼴 가운데 a\mathbf{a} 에 가장 가까운 것을 찾는 문제입니다.

a를 b 위로 사영한 그림자와, 직각으로 남는 잔차

답의 조건은 남은 것이 직교하는 것입니다. 남은 것 a−tb\mathbf{a} - t\mathbf{b} 가 b\mathbf{b} 와 직교해야 하므로

(a−tb)⋅b=0⟹t=a⋅bb⋅b(\mathbf{a} - t\mathbf{b})\cdot\mathbf{b} = 0 \quad \Longrightarrow \quad t = \frac{\mathbf{a}\cdot\mathbf{b}}{\mathbf{b}\cdot\mathbf{b}}

따라서 사영은

proj⁡b(a)=a⋅b∥b∥2 b\operatorname{proj}_{\mathbf{b}}(\mathbf{a}) = \frac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|^2}\,\mathbf{b}

이고, 남은 것을 잔차라고 부릅니다. b\mathbf{b} 가 단위벡터이면 분모가 1이라 (a⋅b)b(\mathbf{a}\cdot\mathbf{b})\mathbf{b} 로 줄고, 앞 절의 좌표 공식이 바로 이 꼴입니다.

손으로 한 번 해 봅니다. a=(3,4)\mathbf{a} = (3,4), b=(2,0)\mathbf{b} = (2,0) 이면 t=64=1.5t = \dfrac{6}{4} = 1.5 이므로 사영은 (3,0)(3,0) 이고 잔차는 (0,4)(0,4) 입니다. 잔차와 b\mathbf{b} 의 내적은 0⋅2+4⋅0=00\cdot2 + 4\cdot0 = 0 으로 직교가 확인됩니다.

스칼라 사영과 벡터 사영

"사영"이라는 말이 두 가지를 가리키고 있어 섞어 쓰면 단위가 안 맞습니다. 갈라 둡니다.

이름 식 결과
스칼라 사영 ∥a∥cos⁡θ=a⋅b∥b∥\|\mathbf{a}\|\cos\theta = \dfrac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|} 수 하나 — 그림자의 길이
벡터 사영 a⋅b∥b∥2 b\dfrac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|^2}\,\mathbf{b} 벡터 — 그림자 그 자체

둘의 관계는 단순합니다. 스칼라 사영에 b\mathbf{b} 방향의 단위벡터를 곱한 것이 벡터 사영입니다. 위의 예로 확인하면 ∥a∥=5\|\mathbf{a}\| = 5, cos⁡θ=6/(5⋅2)=0.6\cos\theta = 6/(5 \cdot 2) = 0.6 이므로 스칼라 사영은 5×0.6=35 \times 0.6 = 3 이고, 여기에 단위벡터 (1,0)(1,0) 을 곱하면 벡터 사영 (3,0)(3,0) 이 됩니다.

스칼라 사영은 음수가 될 수 있다는 점만 기억하면 됩니다. 사잇각이 직각을 넘으면 그림자가 반대 방향으로 지므로 부호가 음이 되고, 그것은 길이가 음수라는 뜻이 아니라 방향이 반대라는 뜻입니다.

잔차가 재는 거리

잔차에는 기하학적 의미가 하나 더 있습니다. 잔차의 노름이 곧 점에서 직선까지의 거리입니다.

b\mathbf{b} 가 만드는 직선 위의 점은 전부 tbt\mathbf{b} 꼴이고, 그중 a\mathbf{a} 에 가장 가까운 것이 사영이었습니다. 그러니 a\mathbf{a} 에서 그 직선까지의 최단 거리는 ∥a−proj⁡b(a)∥\|\mathbf{a} - \operatorname{proj}_{\mathbf{b}}(\mathbf{a})\| 입니다. 위의 예에서 잔차가 (0,4)(0,4) 였으니 거리는 4이고, 실제로 점 (3,4)(3,4) 에서 xx 축까지의 거리가 4입니다.

이 읽기가 중요한 이유는 "가장 가깝다"와 "직교한다"가 같은 말이 되는 자리를 보여 주기 때문입니다. 위에서는 직교를 조건으로 놓고 사영을 유도했는데, 거꾸로 거리를 최소화하는 문제를 풀어도 같은 답이 나옵니다. 넷째 절에서 그 방향을 증명합니다.

길이가 아니라 방향

사영 공식에서 b\mathbf{b} 는 방향만 제공하고 길이는 아무 역할도 하지 않습니다. b\mathbf{b} 를 cbc\mathbf{b} 로 바꿔 넣어 보면 분자에 cc 가 한 번, 분모에 c2c^2 이 한 번, 마지막 벡터에 cc 가 한 번 붙어 전부 지워집니다.

proj⁡cb(a)=a⋅cb∥cb∥2 cb=c (a⋅b)c2∥b∥2 cb=a⋅b∥b∥2 b\operatorname{proj}_{c\mathbf{b}}(\mathbf{a}) = \frac{\mathbf{a}\cdot c\mathbf{b}}{\|c\mathbf{b}\|^2}\,c\mathbf{b} = \frac{c\,(\mathbf{a}\cdot\mathbf{b})}{c^2\|\mathbf{b}\|^2}\,c\mathbf{b} = \frac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|^2}\,\mathbf{b}

cc 가 음수여도 마찬가지입니다. b\mathbf{b} 를 −b-\mathbf{b} 로 뒤집어도 사영은 그대로입니다 — 직선은 그대로이고 그 위에서 가장 가까운 점도 그대로이기 때문입니다.

앞 절에서 스칼라 사영이 부호를 갖는다고 했는데, 여기서는 부호가 지워집니다. 모순이 아니라 재는 대상이 다른 것입니다. 스칼라 사영은 "b\mathbf{b} 가 가리키는 쪽으로 얼마나"를 재므로 b\mathbf{b} 를 뒤집으면 부호가 뒤집히고, 벡터 사영은 직선 위의 한 점을 가리키므로 뒤집어도 같은 점입니다.

부분공간 위로의 사영

이제 방향 하나가 아니라 여러 방향이 만드는 평면 — 행렬 AA 의 열공간 — 위로 사영합니다. AxA\mathbf{x} 꼴 가운데 b\mathbf{b} 에 가장 가까운 것을 찾는 문제입니다.

3차원 벡터 b를 평면 위로 내린 그림자와 평면에 직각인 잔차

조건은 똑같습니다 — 잔차 b−Ax^\mathbf{b} - A\hat{\mathbf{x}} 가 AA 의 모든 열과 직교해야 합니다. 열마다 내적이 0이라는 것을 한 줄로 적으면

A⊤(b−Ax^)=0⟹A⊤A x^=A⊤bA^\top(\mathbf{b} - A\hat{\mathbf{x}}) = \mathbf{0} \quad \Longrightarrow \quad A^\top A\,\hat{\mathbf{x}} = A^\top\mathbf{b}

오른쪽이 정규방정식입니다. A⊤AA^\top A 가 가역이면

x^=(A⊤A)−1A⊤b,Ax^=A(A⊤A)−1A⊤⏟P b\hat{\mathbf{x}} = (A^\top A)^{-1}A^\top\mathbf{b}, \qquad A\hat{\mathbf{x}} = \underbrace{A(A^\top A)^{-1}A^\top}_{P}\,\mathbf{b}

이 PP 가 사영 행렬입니다. 성질 둘이 정의에서 바로 나옵니다.

  • P2=PP^2 = P — 이미 평면 위에 있는 것을 다시 내려도 그대로입니다. 대입해 보면 가운데의 A⊤AA^\top A 와 그 역이 지워집니다.
  • P⊤=PP^\top = P — 전치를 취하면 같은 식이 됩니다.

AA 의 열이 정규직교이면 A⊤A=IA^\top A = I 라 P=AA⊤P = AA^\top 로 줄어듭니다. 역행렬 계산이 통째로 사라지는 것이 정규직교기저를 선호하는 실질적 이유입니다.

AᵀA가 가역일 조건

위 유도에서 "A⊤AA^\top A 가 가역이면"이라고 조건을 달았으니 그것이 언제 성립하는지를 정해 두어야 합니다. 답은 첫 절에서 이미 만난 말입니다 — AA 의 열이 일차독립일 때, 그리고 그때만 가역입니다.

한 방향으로는 두 줄이면 됩니다. A⊤A x=0A^\top A\,\mathbf{x} = \mathbf{0} 이라 하고 양변 왼쪽에 x⊤\mathbf{x}^\top 을 곱하면

x⊤A⊤A x=(Ax)⊤(Ax)=∥Ax∥2=0\mathbf{x}^\top A^\top A\,\mathbf{x} = (A\mathbf{x})^\top(A\mathbf{x}) = \|A\mathbf{x}\|^2 = 0

이므로 Ax=0A\mathbf{x} = \mathbf{0} 입니다. 열이 일차독립이면 이것을 만족하는 것은 x=0\mathbf{x} = \mathbf{0} 뿐이니, A⊤AA^\top A 를 0으로 보내는 벡터가 영벡터뿐이고 따라서 가역입니다.

AA 가 세로로 길쭉한 — 데이터 수가 미지수 수보다 많은 — 모양이라도 이 조건은 자동으로 성립하지 않습니다. 열 하나가 다른 열들의 조합으로 만들어지면 행이 아무리 많아도 종속입니다. 같은 정보를 단위만 바꿔 두 열로 넣은 경우가 대표적입니다.

열이 종속일 때 남는 것

열이 종속이면 무엇이 깨지는지를 정확히 짚어 두면 실수를 줄일 수 있습니다. 깨지는 것은 x^\hat{\mathbf{x}} 이고, 깨지지 않는 것은 사영 Ax^A\hat{\mathbf{x}} 입니다.

A=(121212),b=(132)A = \begin{pmatrix} 1 & 2 \\ 1 & 2 \\ 1 & 2 \end{pmatrix}, \qquad \mathbf{b} = \begin{pmatrix} 1 \\ 3 \\ 2 \end{pmatrix}

둘째 열이 첫째 열의 2배라 AxA\mathbf{x} 는 언제나 (x1+2x2)(1,1,1)(x_1 + 2x_2)(1,1,1) 꼴입니다. 열공간은 (1,1,1)(1,1,1) 이 만드는 직선 하나이고, 그 위로 b\mathbf{b} 를 사영하면 성분의 평균이 2이므로 (2,2,2)(2,2,2) 입니다.

그런데 (2,2,2)(2,2,2) 를 만드는 x^\hat{\mathbf{x}} 는 x1+2x2=2x_1 + 2x_2 = 2 를 만족하는 것이면 무엇이든 됩니다 — (2,0)(2, 0) 도 (0,1)(0, 1) 도 (−2,2)(-2, 2) 도 전부 같은 사영을 줍니다. 계수는 무한히 많은데 그림자는 하나입니다.

이 구별이 실무에서 그대로 나타납니다. 열이 거의 종속인 데이터로 회귀를 돌리면 계수가 크게 흔들리고 부호까지 뒤집히는데, 예측값은 멀쩡합니다. 계수를 해석하려던 사람만 곤란해집니다.

직교여공간과 I − P

PP 가 열공간으로 내리는 행렬이면 I−PI - P 는 무엇일까요. b=Pb+(I−P)b\mathbf{b} = P\mathbf{b} + (I-P)\mathbf{b} 이므로 I−PI - P 는 잔차를 뽑아내는 행렬입니다. 그리고 그것도 사영 행렬입니다.

(I−P)2=I−2P+P2=I−P,(I−P)⊤=I−P(I-P)^2 = I - 2P + P^2 = I - P, \qquad (I-P)^\top = I - P

두 성질이 그대로 성립하니 I−PI-P 도 어딘가로 내리는 사영입니다. 어디로 내릴까요. 잔차는 AA 의 모든 열과 직교하므로, 열공간의 모든 벡터와 직교하는 벡터들이 모인 곳으로 내립니다. 이 공간을 열공간의 직교여공간이라 합니다.

그래서 임의의 벡터가 두 조각으로 딱 갈립니다 — 열공간 안의 조각과 직교여공간 안의 조각. 둘은 서로 직교하므로 피타고라스가 성립합니다.

∥b∥2=∥Pb∥2+∥(I−P)b∥2\|\mathbf{b}\|^2 = \|P\mathbf{b}\|^2 + \|(I-P)\mathbf{b}\|^2

이 한 줄이 다음 절의 재료입니다. "설명된 부분과 설명 안 된 부분"이라는 회귀의 말이 여기서 나옵니다.

그람-슈미트와 QR

P=AA⊤P = AA^\top 로 줄어드는 정규직교 열은 운이 좋아야 얻는 것이 아니라 만들 수 있습니다. 절차는 이미 손에 있는 사영입니다.

첫 열을 그대로 두고 길이를 1로 맞춥니다. 둘째 열에서는 첫 방향으로의 사영을 빼서 남은 잔차만 취하고 길이를 1로 맞춥니다. 셋째 열에서는 앞의 두 방향으로의 사영을 빼고 남은 것을 취합니다. 이렇게 사영을 빼는 일을 되풀이해 정규직교기저를 만드는 절차를 그람-슈미트라고 합니다.

빼는 과정에서 "몇 배를 뺐는가"를 버리지 않고 모아 두면 원래 행렬을 되살릴 수 있습니다.

A=QRA = QR

QQ 는 만들어진 정규직교 열들이고 RR 은 뺀 배수들을 모은 위삼각행렬입니다. 이것이 QR 분해입니다.

여기서 한 가지를 혼동하지 말아야 합니다. AA 가 m×nm \times n 이고 m>nm > n 이면 QQ 도 세로로 길쭉하고, 이때

Q⊤Q=In이지만QQ⊤≠ImQ^\top Q = I_n \quad \text{이지만} \quad QQ^\top \ne I_m

입니다. 앞의 것은 열이 정규직교라는 뜻이고, 뒤의 것은 QQ 의 열이 공간 전체를 채운다는 뜻이라 서로 다른 주장입니다. 실제로 QQ⊤QQ^\top 은 단위행렬이 아니라 앞에서 만든 사영 행렬 PP 입니다 — 열공간 위로 내릴 뿐 공간 전체를 그대로 두지는 않습니다. QQ 가 정사각일 때만 둘이 함께 성립합니다.

최소제곱의 기하

x^\hat{\mathbf{x}} 를 "잔차가 직교하도록" 정했는데, 그것이 정말 가장 가까운 점인지는 따로 보여야 합니다. 피타고라스 한 줄이면 됩니다.

임의의 x\mathbf{x} 에 대해 b−Ax\mathbf{b} - A\mathbf{x} 를 두 조각으로 나눕니다.

b−Ax=(b−Ax^)⏟평면에 직교+A(x^−x)⏟평면 안\mathbf{b} - A\mathbf{x} = \underbrace{(\mathbf{b} - A\hat{\mathbf{x}})}_{\text{평면에 직교}} + \underbrace{A(\hat{\mathbf{x}} - \mathbf{x})}_{\text{평면 안}}

앞 조각은 열공간과 직교하고 뒤 조각은 열공간 안에 있으니 둘이 직교합니다. 그러면 첫 절의 피타고라스가 그대로 적용되어

∥b−Ax∥2=∥b−Ax^∥2+∥A(x^−x)∥2 ≥ ∥b−Ax^∥2\|\mathbf{b} - A\mathbf{x}\|^2 = \|\mathbf{b} - A\hat{\mathbf{x}}\|^2 + \|A(\hat{\mathbf{x}} - \mathbf{x})\|^2 \ \ge\ \|\mathbf{b} - A\hat{\mathbf{x}}\|^2

등호는 Ax=Ax^A\mathbf{x} = A\hat{\mathbf{x}} 일 때뿐입니다. 미분을 한 번도 쓰지 않고 최소를 증명했습니다 — 최소제곱은 최적화 문제이기 전에 기하 문제입니다.

손으로 따라가는 최소제곱

세 점 (0,1)(0,1), (1,3)(1,3), (2,2)(2,2) 에 직선 y=c+mty = c + mt 를 맞춥니다. 미지수는 cc 와 mm 이고

A=(101112),b=(132)A = \begin{pmatrix} 1 & 0 \\ 1 & 1 \\ 1 & 2 \end{pmatrix}, \qquad \mathbf{b} = \begin{pmatrix} 1 \\ 3 \\ 2 \end{pmatrix}

정규방정식의 재료를 구합니다.

A⊤A=(3335),A⊤b=(67)A^\top A = \begin{pmatrix} 3 & 3 \\ 3 & 5 \end{pmatrix}, \qquad A^\top\mathbf{b} = \begin{pmatrix} 6 \\ 7 \end{pmatrix}

A⊤AA^\top A 의 행렬식이 15−9=615 - 9 = 6 이므로 역행렬이 16(5−3−33)\dfrac16\begin{pmatrix} 5 & -3 \\ -3 & 3\end{pmatrix} 이고

x^=16(5−3−33)(67)=16(93)=(1.50.5)\hat{\mathbf{x}} = \frac16\begin{pmatrix} 5 & -3 \\ -3 & 3\end{pmatrix}\begin{pmatrix} 6 \\ 7 \end{pmatrix} = \frac16\begin{pmatrix} 9 \\ 3 \end{pmatrix} = \begin{pmatrix} 1.5 \\ 0.5 \end{pmatrix}

맞춘 직선은 y=1.5+0.5ty = 1.5 + 0.5t 이고 예측값은 (1.5, 2, 2.5)(1.5,\ 2,\ 2.5), 잔차는

r=b−Ax^=(−0.5, 1, −0.5)\mathbf{r} = \mathbf{b} - A\hat{\mathbf{x}} = (-0.5,\ 1,\ -0.5)

세 점에 맞춘 직선과 잔차 (−0.5, 1, −0.5)

잔차가 두 열과 직교하는지 확인합니다.

r⋅(1,1,1)=−0.5+1−0.5=0,r⋅(0,1,2)=0+1−1=0\mathbf{r}\cdot(1,1,1) = -0.5 + 1 - 0.5 = 0, \qquad \mathbf{r}\cdot(0,1,2) = 0 + 1 - 1 = 0

둘 다 0입니다. 첫째 식이 잔차의 합이 0이라는 것이고 — 절편 열이 상수 열이기 때문입니다 — 둘째 식이 잔차가 입력과 상관이 없다는 것입니다. 회귀에서 늘 인용되는 이 두 성질은 통계적 가정이 아니라 사영의 정의에서 나오는 기하적 사실입니다.

남은 오차의 크기

최소 잔차제곱합이 얼마인지도 피타고라스가 곧바로 답합니다. 앞 절의 ∥b∥2=∥Pb∥2+∥r∥2\|\mathbf{b}\|^2 = \|P\mathbf{b}\|^2 + \|\mathbf{r}\|^2 을 옮겨 적으면

∥r∥2=∥b∥2−∥Pb∥2\|\mathbf{r}\|^2 = \|\mathbf{b}\|^2 - \|P\mathbf{b}\|^2

입니다. 잔차를 계산하지 않고도 그 크기를 알 수 있다는 뜻입니다. 위의 예로 확인합니다.

∥b∥2=1+9+4=14,∥Pb∥2=1.52+22+2.52=12.5\|\mathbf{b}\|^2 = 1 + 9 + 4 = 14, \qquad \|P\mathbf{b}\|^2 = 1.5^2 + 2^2 + 2.5^2 = 12.5

∥r∥2=14−12.5=1.5\|\mathbf{r}\|^2 = 14 - 12.5 = 1.5

실제 잔차 (−0.5,1,−0.5)(-0.5, 1, -0.5) 의 제곱합도 0.25+1+0.25=1.50.25 + 1 + 0.25 = 1.5 로 같습니다.

결정계수로 읽기

이 식을 비로 바꾸면 회귀에서 쓰는 지표가 나옵니다. 다만 그냥 ∥Pb∥2/∥b∥2\|P\mathbf{b}\|^2/\|\mathbf{b}\|^2 를 쓰지는 않습니다 — 상수 열이 들어 있으면 평균만 맞춰도 이 비가 이미 1에 가까워져 아무것도 구별하지 못하기 때문입니다.

그래서 평균을 뺀 뒤에 같은 셈을 합니다. bˉ\bar{b} 를 성분의 평균이라 할 때

R2=1−∥r∥2∥b−bˉ1∥2R^2 = 1 - \frac{\|\mathbf{r}\|^2}{\|\mathbf{b} - \bar{b}\mathbf{1}\|^2}

이 값이 결정계수이고, "평균으로만 맞추던 것에 견줘 오차를 얼마나 줄였는가"를 읽습니다. 위의 예에서 평균이 2이므로 b−2⋅1=(−1,1,0)\mathbf{b} - 2\cdot\mathbf{1} = (-1, 1, 0) 이고 그 제곱합이 2입니다.

R2=1−1.52=0.25R^2 = 1 - \frac{1.5}{2} = 0.25

세 점이 직선에서 꽤 벗어나 있으니 낮게 나오는 것이 맞습니다. 여기서 볼 것은 숫자가 아니라 결정계수가 통계 지표이기 전에 직각삼각형의 변의 비라는 점입니다.

정규방정식을 피하는 이유

A⊤Ax^=A⊤bA^\top A\hat{\mathbf{x}} = A^\top \mathbf{b} 를 그대로 푸는 것은 손으로는 자연스럽지만 수치적으로는 나쁜 선택입니다. A⊤AA^\top A 를 만드는 순간 조건수가 제곱되기 때문입니다.

조건수는 입력의 작은 흔들림이 답을 얼마나 크게 흔드는지를 재는 값인데, AA 의 특이값이 σ\sigma 이면 A⊤AA^\top A 의 고윳값은 σ2\sigma^2 이라 최대와 최소의 비도 함께 제곱됩니다.

κ(A⊤A)=κ(A)2\kappa(A^\top A) = \kappa(A)^2

κ(A)\kappa(A) 가 10310^3 이면 κ(A⊤A)\kappa(A^\top A) 는 10610^6 입니다. 배정밀도로도 유효 자릿수를 절반 넘게 잃는 규모입니다. 그래서 실제 계산에서는 A⊤AA^\top A 를 만들지 않고 앞에서 본 QRQR 로 갑니다 — A=QRA = QR 을 넣으면 정규방정식이 Rx^=Q⊤bR\hat{\mathbf{x}} = Q^\top\mathbf{b} 로 줄고, 이것은 위삼각이라 후진 대입만으로 풀립니다. QQ 가 노름을 보존하므로 조건수도 제곱되지 않습니다.

코드로 확인하기

import numpy as np

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

P = A @ np.linalg.inv(A.T @ A) @ A.T
r = b - P @ b

print(P @ b)                        # [1.5 2.  2.5]
print(r)                            # [-0.5  1.  -0.5]
print(A.T @ r)                      # [0. 0.]  잔차가 모든 열과 직교
print(np.allclose(P @ P, P))        # True

직교행렬이 노름을 보존하는 것도 한 줄로 확인됩니다.

Q = np.array([[0.6, -0.8], [0.8, 0.6]])
v = np.array([3., 1.])

print(Q.T @ Q)                                          # [[1 0] [0 1]]
print(np.linalg.norm(v), np.linalg.norm(Q @ v))         # 3.1623 3.1623

고차원의 코사인 재기

첫 절의 1/d1/\sqrt{d} 를 직접 재 봅니다. 차원마다 무작위 단위벡터 쌍을 많이 만들어 코사인의 흩어진 정도를 구하고, 이론값으로 나눠 봅니다.

rng = np.random.default_rng(0)

for d in [2, 10, 100, 1000, 10000]:
    a = rng.standard_normal((200_000, d))
    c = rng.standard_normal((200_000, d))
    a /= np.linalg.norm(a, axis=1, keepdims=True)
    c /= np.linalg.norm(c, axis=1, keepdims=True)
    cos = (a * c).sum(axis=1)
    print(f"d={d:6d}  1/√d = {1/np.sqrt(d):.4f}   실측/이론 = {cos.std()*np.sqrt(d):.2f}")

# d=     2  1/√d = 0.7071   실측/이론 = 1.00
# d=    10  1/√d = 0.3162   실측/이론 = 1.00
# d=   100  1/√d = 0.1000   실측/이론 = 1.00
# d=  1000  1/√d = 0.0316   실측/이론 = 1.00
# d= 10000  1/√d = 0.0100   실측/이론 = 1.00

비가 어느 차원에서나 1이라는 것이 1/d1/\sqrt{d} 가 맞는 규모라는 뜻입니다. 차원이 1만이면 무작위 두 벡터의 코사인이 0.01 언저리 — 검색에서 "닮았다"고 부를 만한 값과는 두 자릿수 차이입니다.

조건수가 제곱되는 것

κ(A⊤A)=κ(A)2\kappa(A^\top A) = \kappa(A)^2 을 두 행렬로 확인합니다. 하나는 앞에서 쓴 얌전한 AA 이고, 다른 하나는 두 열이 거의 같은 방향인 행렬입니다.

A = np.array([[1., 0], [1, 1], [1, 2]])
B = np.array([[1., 1], [0, 1e-3]])

for M, name in [(A, "A"), (B, "B")]:
    k1 = np.linalg.cond(M)
    k2 = np.linalg.cond(M.T @ M)
    print(f"{name}: cond={k1:9.3f}  cond(MtM)={k2:12.3f}  비={k2/k1**2:.4f}")

# A: cond=    2.924  cond(MtM)=       8.550  비=1.0000
# B: cond= 2000.001  cond(MtM)= 4000005.000  비=1.0000

BB 에서 조건수가 2천에서 400만으로 뜁니다. 마지막으로 두 풀이의 답을 견줍니다.

b = np.array([1., 3, 2])

x_normal = np.linalg.solve(A.T @ A, A.T @ b)
x_lstsq = np.linalg.lstsq(A, b, rcond=None)[0]

print(x_normal, x_lstsq)     # [1.5 0.5] [1.5 0.5]

조건수가 작은 AA 에서는 두 답이 같습니다. lstsq 가 안쪽에서 A⊤AA^\top A 를 만들지 않는 방식을 쓰므로, 갈리는 것은 조건수가 큰 행렬에서입니다. 그래서 손으로 유도할 때는 정규방정식으로 하고 코드로 풀 때는 lstsq 를 쓰는 것이 기본입니다.

직교 초기화

이제 첫머리의 nn.init.orthogonal_ 을 읽을 수 있습니다. 그 함수는 가중치 행렬 WW 를 W⊤W=IW^\top W = I 가 되도록 채웁니다. 그러면 위에서 본 대로 ∥Wx∥=∥x∥\|W\mathbf{x}\| = \|\mathbf{x}\| 입니다.

이것이 왜 초기화에 쓸모 있는지는 층을 여럿 쌓아 보면 보입니다. 층마다 벡터의 크기가 α\alpha 배씩 되면 LL 층을 지난 뒤에는 αL\alpha^L 배입니다. α\alpha 가 1보다 조금만 커도 폭발하고, 조금만 작아도 0으로 죽습니다. 직교행렬은 α\alpha 를 정확히 1로 못 박습니다 — 특이값이 전부 1이라 어느 방향으로도 늘이거나 줄이지 않습니다. 같은 성질이 역전파에도 그대로 적용되어 기울기의 크기도 보존됩니다.

직교 정규화는 학습 중에도 이 성질을 유지하려는 시도입니다. ∥W⊤W−I∥2\|W^\top W - I\|^2 같은 항을 손실에 더해 WW 가 직교에서 멀어지지 않도록 당깁니다. 초기화는 0스텝에서만 참이고 학습이 진행되면 깨지기 때문입니다.

정사각이 아닌 W

실제 층의 가중치는 입력과 출력의 차원이 달라 정사각이 아닌 경우가 훨씬 많습니다. 그때는 앞 절의 Q⊤QQ^\top Q 와 QQ⊤QQ^\top 의 구별이 그대로 되살아납니다.

WW 의 모양 성립하는 것 결과
세로로 길쭉 (출력 차원이 더 큼) W⊤W=IW^\top W = I 모든 입력에 대해 노름이 보존된다
가로로 납작 (출력 차원이 더 작음) WW⊤=IWW^\top = I 일부 방향은 보존되고, 나머지는 0으로 눌린다

가로로 납작한 경우가 중요합니다. 차원을 줄이는 층이므로 어떤 입력은 반드시 버려질 수밖에 없고, 버려지는 방향에서는 노름이 0이 됩니다. "직교 초기화를 했으니 크기가 보존된다"는 말이 그대로 참인 것은 앞의 경우뿐이고, 뒤의 경우에는 살아남은 방향에 한해 참입니다.

그래서 깊은 망에서 직교 초기화의 효과를 기대하려면 차원이 크게 줄어드는 층에 주의해야 합니다. 그 층에서 잘려 나간 방향은 다음 층의 직교성과 무관하게 이미 사라진 뒤입니다.

gain으로 보정하기

노름을 정확히 1로 맞춰 두어도 층 사이에 활성함수가 끼면 크기가 다시 줄어듭니다. ReLU가 대표적입니다 — 음수를 전부 0으로 만들어 버리므로, 대칭인 입력이 들어오면 평균적으로 절반이 죽어 분산이 반으로 줍니다.

그래서 초기화 함수에 gain 이라는 배수가 붙어 있습니다.

nn.init.orthogonal_(self.weight, gain=nn.init.calculate_gain('relu'))

ReLU에 대한 값은 2\sqrt{2} 입니다. 분산이 절반이 되므로 표준편차는 1/21/\sqrt{2} 배가 되고, 그것을 되돌리려면 2\sqrt{2} 를 곱해야 하기 때문입니다. tanh처럼 원점 근처에서 기울기가 1에 가까운 함수는 gain이 1 언저리이고, 활성함수가 크기를 얼마나 줄이는지에 따라 값이 달라집니다.

여기서 직교성 자체는 gain과 무관하다는 점을 짚어 둡니다. 2W\sqrt{2}W 는 더 이상 W⊤W=IW^\top W = I 를 만족하지 않지만 열끼리는 여전히 직교합니다. 보존되는 것은 "방향들이 서로 수직"이고, 바뀌는 것은 배율뿐입니다.

학습이 진행되면

초기화는 0스텝의 이야기이므로, 학습이 진행되면서 직교성이 얼마나 빨리 깨지는지를 재 보고 싶어집니다. 재는 방법은 둘입니다.

  • 특이값의 분포를 직접 본다. 처음에는 전부 1인데 학습이 진행되면 흩어집니다. 가장 큰 것과 가장 작은 것의 비 — 곧 조건수 — 가 1에서 얼마나 멀어졌는지가 한 숫자 요약입니다.
  • ∥W⊤W−I∥\|W^\top W - I\| 를 잰다. 직교 정규화에서 손실에 더하는 그 항을 벌점으로 쓰지 않고 지표로만 읽는 것입니다. 0이면 완전히 직교이고, 커질수록 멀어진 것입니다.

두 지표가 같은 것을 다르게 요약합니다. 조건수는 가장 나쁜 방향 하나를 보고, ∥W⊤W−I∥\|W^\top W - I\| 는 전체의 평균적인 어긋남을 봅니다. 한 방향만 크게 망가진 경우와 모든 방향이 조금씩 어긋난 경우를 구별하려면 둘을 함께 봐야 합니다.

멀티헤드와 직교

마지막으로 첫머리의 두 번째 물음입니다. 멀티헤드 어텐션을 두고 "헤드마다 서로 다른 것을 본다"고 말할 때, 그것은 직교와 다릅니다.

멀티헤드는 dmodeld_{\text{model}} 차원을 hh 조각으로 나눠 각 헤드에 dk=dmodel/hd_k = d_{\text{model}}/h 차원을 줍니다. 헤드 ii 가 보는 것은 WQ(i)W_Q^{(i)} 가 만드는 부분공간인데, 이 행렬들은 학습으로 정해질 뿐 직교하도록 강제되지 않습니다. 구조 어디에도 WQ(i)⊤WQ(j)=0W_Q^{(i)\top}W_Q^{(j)} = 0 을 요구하는 항이 없습니다.

직교 사영 멀티헤드의 분할
부분공간의 관계 서로 직교하도록 강제 학습된 절단이라 겹칠 수 있다
정보의 중복 없음 — 잔차가 직교한다 있을 수 있다 — 실제로 비슷한 헤드가 관찰된다
합치는 방법 직교 성분의 단순한 합 WOW_O 가 학습으로 섞는다

"다른 것을 본다"는 관찰이지 제약이 아닙니다. 헤드들이 실제로 직교하는지 알고 싶으면 재 봐야 하고, 재는 도구가 방금 만든 사영입니다 — 한 헤드의 출력을 다른 헤드의 부분공간 위로 사영해 잔차의 노름을 보면 됩니다. 잔차가 거의 0이면 그 헤드는 새로 보태는 것이 없다는 뜻입니다.

헤드를 지워 보는 실험

겹침을 재는 더 거친 방법이 있습니다. 헤드를 하나씩 지우고 성능이 얼마나 떨어지는지 보는 것입니다. 학습된 트랜스포머에서 이 실험을 하면 상당수의 헤드를 지워도 성능이 크게 떨어지지 않는다는 결과가 되풀이해 보고되었습니다.

이 관찰을 겹침의 증거로 읽는 것은 자연스럽습니다. 헤드 하나를 지웠는데 손실이 그대로라면, 그 헤드가 하던 일을 다른 헤드가 이미 하고 있었다는 뜻이기 때문입니다. 직교하는 부분공간으로 갈라져 있었다면 하나를 지울 때 그 방향의 정보가 통째로 사라져 손실이 올라가야 합니다.

다만 이 실험이 곧바로 직교성의 측정은 아닙니다. 지워도 손실이 그대로인 이유가 겹침이 아니라 그 헤드가 처음부터 거의 아무 일도 안 하고 있었기 때문일 수도 있습니다. 둘을 가르려면 앞에서 말한 사영으로 잔차를 직접 재야 합니다. 지우기 실험은 "무언가 겹쳐 있다"까지만 말해 줍니다.

겹치는 것의 이득

겹침을 낭비로만 읽으면 절반만 본 것입니다. 같은 것을 여럿이 보면 잡음에 강해집니다.

직교하는 분해에서는 한 방향의 정보가 한 곳에만 담깁니다. 효율은 최대인데, 그 한 곳이 흔들리면 그 정보가 통째로 흔들립니다. 겹쳐 있으면 한 헤드가 잘못 봐도 다른 헤드가 같은 것을 보고 있으므로 평균이 덜 흔들립니다. 저장 효율을 내주고 안정성을 산 셈입니다.

그래서 앞의 지우기 실험 결과를 "헤드가 낭비되고 있다"로만 읽는 것은 성급합니다. 겹침은 학습이 고른 절충이고, 얼마나 겹칠지는 데이터의 잡음 정도에 따라 달라집니다.

직교를 강제한 변형

그래도 겹침을 줄이고 싶다면 직교 정규화에서 쓴 것과 같은 방법을 헤드에 적용할 수 있습니다. 헤드 쌍마다 ∥WQ(i)⊤WQ(j)∥2\|W_Q^{(i)\top}W_Q^{(j)}\|^2 같은 항을 손실에 더해 서로 밀어내는 것입니다. 이런 변형은 실제로 제안되어 왔고, 헤드가 더 다양한 것을 보게 만드는 데 효과가 있습니다.

무엇을 내주는지도 분명합니다.

  • 위의 안정성을 잃습니다. 겹침이 줄면 한 헤드의 실수를 덮어 줄 다른 헤드가 없습니다.
  • 조율할 값이 하나 늘어납니다. 벌점의 가중치가 너무 크면 본래의 손실을 밀어내고, 너무 작으면 아무 일도 하지 않습니다.
  • 계산이 늘어납니다. 헤드 쌍마다 항을 계산해야 하므로 헤드 수의 제곱에 비례합니다.

정리하면, 직교는 공짜로 얻는 성질이 아니라 값을 치르고 사는 제약입니다. 초기화에서 값이 싼 이유는 0스텝에서 한 번만 강제하기 때문이고, 학습 내내 강제하려면 위의 셋을 내줘야 합니다.

정리

  • 직교는 a⋅b=0\mathbf{a}\cdot\mathbf{b} = 0 입니다. 각도를 재지 않고 확인되며, 피타고라스가 성립하는 조건과 같은 말입니다. 영벡터가 모든 것과 직교하는 것도 이 정의라서 탈이 없습니다.
  • 정규직교기저에서는 좌표가 내적 한 번입니다. Q⊤Q=IQ^\top Q = I 이고 ∥Qx∥=∥x∥\|Q\mathbf{x}\| = \|\mathbf{x}\| 입니다. 0이 아닌 벡터들이 서로 직교하면 그것만으로 일차독립입니다.
  • 차원이 커지면 무작위 두 벡터의 코사인이 1/d1/\sqrt{d} 규모로 작아집니다. 고차원에서 "직교한다"는 관찰 자체는 정보가 거의 없습니다.
  • 사영은 "남은 것이 직교하도록"이라는 조건 하나에서 유도됩니다. 한 벡터 위로는 a⋅b∥b∥2b\dfrac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|^2}\mathbf{b} 이고 b\mathbf{b} 의 길이와 부호는 아무 역할도 하지 않습니다. 잔차의 노름이 점과 직선 사이의 거리입니다.
  • 부분공간 위로는 P=A(A⊤A)−1A⊤P = A(A^\top A)^{-1}A^\top 이고 P2=PP^2 = P, P⊤=PP^\top = P 입니다. A⊤AA^\top A 가 가역일 조건은 AA 의 열이 일차독립인 것이고, 종속이면 x^\hat{\mathbf{x}} 는 안 정해져도 사영은 그대로입니다. I−PI - P 도 사영 행렬이고 직교여공간으로 내립니다.
  • 그람-슈미트는 사영을 되풀이해 정규직교기저를 만드는 절차이고, 그 기록이 QR 분해입니다. Q⊤Q=IQ^\top Q = I 와 QQ⊤=IQQ^\top = I 는 다른 주장이라 정사각이 아니면 뒤의 것이 깨집니다.
  • 최소제곱은 미분 없이 피타고라스로 증명됩니다. 잔차의 합이 0이고 잔차가 입력과 직교한다는 성질은 통계가 아니라 기하에서 나옵니다. 최소 잔차제곱합이 ∥b∥2−∥Pb∥2\|\mathbf{b}\|^2 - \|P\mathbf{b}\|^2 이고, 평균을 뺀 뒤 같은 비를 읽으면 결정계수입니다.
  • 정규방정식을 그대로 푸는 것은 조건수를 제곱하므로 실제 계산은 QRQR 로 갑니다.
  • 직교 초기화는 ∥Wx∥=∥x∥\|W\mathbf{x}\| = \|\mathbf{x}\| 를 못 박는 일이라 층을 쌓아도 신호와 기울기의 크기가 유지됩니다. 정사각이 아니면 한쪽 방향만 보존되고, 활성함수가 크기를 줄이므로 gain을 곱해 보정합니다. ReLU에는 2\sqrt{2} 입니다.
  • 멀티헤드의 "다른 것을 본다"는 직교가 아닙니다. 학습된 절단이라 겹칠 수 있고, 겹침은 저장 효율을 내주고 잡음에 대한 안정성을 산 절충입니다. 직교를 손실 항으로 강제할 수는 있지만 그 안정성과 조율 비용을 내줘야 합니다.

다음 글은 여기서 계속 쓴 "부분공간"과 "기저"라는 말을 제대로 정의합니다 — 스팬·기저·좌표가 그 자리입니다.


읽어주셔서 감사합니다. 😊

LATEST

수학의 최신 글

수학2026.09.07

양자화 오차: 격자 사상, 오차 분산, 이상치 채널

실수를 2^b개 격자에 사상할 때 오차의 분산이 왜 Δ²/12인지 유도하고, 그것이 비트당 6.02dB라는 SNR로 번역되는 과정을 실측과 대조했습니다. 이상치 하나가 나머지 값의 유효 비트를 어떻게 먹는지, 그리고 int4에서 성능이 무너지는 지점을 오차 예산으로 미리 계산하는 법까지.

중급18 MIN
수학2026.09.07

수치적으로 안정한 계산 패턴 모음

최댓값 빼기, 로그 공간, log1p·expm1, 분산의 두 공식, 정규화의 ε, fp32 누산, 역행렬 대신 solve — 프레임워크가 몰래 해 주는 일곱 가지를 하나씩 꺼내 각각 어떤 고장을 막는지 직접 재 봤습니다. 수식을 그대로 옮긴 코드가 왜 라이브러리보다 나쁜지에 대한 목록입니다.

중급22 MIN
수학2026.09.07

부동소수점은 어디서 새는가: 반올림, 상쇄, 더하는 순서

0.1 + 0.2가 0.3이 아닌 이유부터 시작해 머신 엡실론을 유도하고, 같은 16비트인데 fp16과 bf16이 서로 다른 지점에서 터지는 이유, 비슷한 수를 뺄 때 유효자리가 사라지는 파괴적 상쇄, 그리고 1,000만 개를 순서만 바꿔 더했을 때 오차가 백만 배 갈리는 실험까지 직접 재 봤습니다.

중급23 MIN