수학

MATH / 중급 8번

생성·일차독립·기저: 같은 벡터를 다른 축으로 다시 쓰기

생성(span)과 일차독립을 정의하고 거기서 기저와 차원을 세웁니다. 좌표가 벡터가 아니라 기저에 붙는 값이라는 것을 손계산으로 확인하고, 좌표변환 행렬 B와 그 역을 유도합니다. 임베딩이 실제로 몇 차원을 쓰는가라는 질문이 왜 일차독립의 질문인지도 짚습니다.

PALDYN Team19 MIN READ

문장 임베딩 모델을 하나 불러오면 벡터가 768차원으로 나옵니다. 그런데 임베딩 공간을 실제로 들여다본 보고들은 한결같이 같은 말을 합니다 — 그 벡터들이 공간 전체에 퍼져 있지 않고 좁은 원뿔 안에 몰려 있다는 것입니다. 서로 다른 문장 두 개를 아무렇게나 뽑아 코사인 유사도를 재면 0 근처가 아니라 0.6, 0.7 같은 값이 나옵니다.

여기서 "768개를 준다고 768개를 쓰는 것은 아니다"라는 말이 나오고, 이것을 표현 붕괴라고 부릅니다. 그런데 이 문장은 아직 정확하지 않습니다. "몇 차원을 쓴다"는 것이 무엇을 세는 말인지를 먼저 정해야 합니다.

이 글이 그 도구를 만듭니다. 지난 글에서 "부분공간"과 "기저"라는 말을 정의 없이 계속 썼는데, 그 빚도 여기서 갚습니다.

생성 — 그 벡터들로 만들 수 있는 것 전부

벡터 몇 개를 손에 쥐었을 때 "이것들로 무엇을 만들 수 있는가"를 묻습니다. 만들 수 있는 것은 선형결합, 즉 각자에 수를 곱해 더한 것 전부입니다.

정의. 벡터 v1,…,vk\mathbf{v}_1, \ldots, \mathbf{v}_k 의 생성(span)은 그것들의 선형결합 전체가 이루는 집합이다.

span⁡{v1,…,vk}={ c1v1+⋯+ckvk ∣ ci∈R }\operatorname{span}\{\mathbf{v}_1,\ldots,\mathbf{v}_k\} = \{\, c_1\mathbf{v}_1 + \cdots + c_k\mathbf{v}_k \ \mid\ c_i \in \mathbb{R} \,\}

계수 cic_i 는 아무 실수나 되므로 span은 원점을 지나는 직선, 평면, 그 이상이 됩니다. 이렇게 덧셈과 스칼라배에 닫힌 집합을 부분공간이라고 부릅니다 — 지난 글에서 "평면 위로 사영한다"고 할 때의 그 평면이 정확히 이것입니다.

벡터 하나의 span은 직선, 나란하지 않은 둘의 span은 평면, 나란한 둘의 span은 여전히 직선

그림의 세 번째 칸이 중요합니다. 벡터를 하나 더 준다고 span이 반드시 커지지는 않습니다. w=−v\mathbf{w} = -\mathbf{v} 이면 c1v+c2w=(c1−c2)vc_1\mathbf{v} + c_2\mathbf{w} = (c_1 - c_2)\mathbf{v} 라서 결국 v\mathbf{v} 의 배수만 나옵니다. 이미 만들 수 있던 것을 다시 만들 뿐입니다.

span이 커지는 것은 새 방향을 보탤 때뿐이고, "새 방향인가"를 판정하는 말이 다음 절의 일차독립입니다.

일차독립 — 낭비가 없다는 조건

"w\mathbf{w} 가 나머지로 만들어지는가"를 벡터마다 따로 확인하는 것은 번거롭습니다. 한 줄로 묶은 조건이 있습니다.

정의. c1v1+⋯+ckvk=0c_1\mathbf{v}_1 + \cdots + c_k\mathbf{v}_k = \mathbf{0} 의 해가 c1=⋯=ck=0c_1 = \cdots = c_k = 0 하나뿐이면 이 벡터들이 일차독립이라고 한다. 그렇지 않으면 일차종속이다.

계수를 전부 0으로 두면 합이 0\mathbf{0} 인 것은 언제나 참이라 이 해는 늘 있습니다. 그래서 이것을 자명한 해라고 부르고, 질문은 "그 말고 다른 해가 있는가"입니다.

왜 이 조건이 '낭비가 없다'와 같은 말인지는 한 줄로 보입니다. 자명하지 않은 해가 있어서 예컨대 c1≠0c_1 \ne 0 이라면 양변을 c1c_1 로 나눠

v1=−c2c1v2−⋯−ckc1vk\mathbf{v}_1 = -\frac{c_2}{c_1}\mathbf{v}_2 - \cdots - \frac{c_k}{c_1}\mathbf{v}_k

즉 v1\mathbf{v}_1 이 나머지의 선형결합이 됩니다. 그 벡터는 span에 아무것도 보태지 않으므로 빼도 됩니다. 거꾸로 하나라도 나머지로 만들어지면 그 식을 한쪽으로 옮겨 자명하지 않은 해를 얻습니다. 두 진술은 정확히 같습니다.

(1,2)와 (2,4)는 한 직선 위라 종속, (1,2)와 (2,1)은 평면을 만들어 독립

손으로 한 번. (1,2)(1,2) 와 (2,4)(2,4) 는 2(1,2)−1(2,4)=(0,0)2(1,2) - 1(2,4) = (0,0) 이므로 계수가 (2,−1)(2,-1) 인 자명하지 않은 해가 있습니다 — 종속입니다. (1,2)(1,2) 와 (2,1)(2,1) 은 어떨까요.

c1(1,2)+c2(2,1)=(0,0)⟹{c1+2c2=02c1+c2=0c_1(1,2) + c_2(2,1) = (0,0) \quad \Longrightarrow \quad \begin{cases} c_1 + 2c_2 = 0 \\ 2c_1 + c_2 = 0\end{cases}

첫 식에서 c1=−2c2c_1 = -2c_2 를 둘째에 넣으면 −4c2+c2=−3c2=0-4c_2 + c_2 = -3c_2 = 0 이라 c2=0c_2 = 0, 따라서 c1=0c_1 = 0 입니다. 자명한 해뿐이므로 독립입니다.

셋을 주면 어떻게 될까요. (1,2)(1,2), (2,1)(2,1), (4,5)(4,5) 에서 c3=1c_3 = 1 로 두고 풀면

{c1+2c2+4=02c1+c2+5=0⟹c1=−2, c2=−1\begin{cases} c_1 + 2c_2 + 4 = 0 \\ 2c_1 + c_2 + 5 = 0\end{cases} \quad \Longrightarrow \quad c_1 = -2,\ c_2 = -1

확인하면 −2(1,2)−1(2,1)+1(4,5)=(0,0)-2(1,2) - 1(2,1) + 1(4,5) = (0,0) 입니다. 종속입니다. 이것은 우연이 아닙니다 — Rn\mathbb{R}^n 에서 벡터를 n+1n+1 개 이상 모으면 반드시 종속입니다. 미지수 kk 개에 식이 nn 개인 제차연립방정식이고, 식보다 미지수가 많으면 자유변수가 남아 자명하지 않은 해가 생기기 때문입니다.

기저와 차원

이제 두 성질을 한 대상에 겹쳐 놓습니다.

정의. 부분공간 VV 에 대해 일차독립이면서 VV 를 생성하는 벡터들의 모음을 VV 의 기저라고 한다.

생성한다는 것은 "빠짐없이 만든다"이고 독립이라는 것은 "군더더기가 없다"입니다. 둘을 함께 요구하면 좋은 성질이 하나 따라옵니다.

기저로 적는 방법은 하나뿐입니다. x\mathbf{x} 를 두 가지로 적었다고 해 봅시다.

x=a1b1+⋯+anbn=c1b1+⋯+cnbn\mathbf{x} = a_1\mathbf{b}_1 + \cdots + a_n\mathbf{b}_n = c_1\mathbf{b}_1 + \cdots + c_n\mathbf{b}_n

빼면 (a1−c1)b1+⋯+(an−cn)bn=0(a_1 - c_1)\mathbf{b}_1 + \cdots + (a_n - c_n)\mathbf{b}_n = \mathbf{0} 이고, 기저는 독립이므로 계수가 전부 0이어야 합니다. 즉 ai=cia_i = c_i 입니다. 표현이 유일하다는 것이 독립을 요구한 대가로 얻는 것이고, 다음 절의 좌표는 이 유일성 위에 서 있습니다.

한 부분공간의 기저는 여럿입니다. R2\mathbb{R}^2 는 {(1,0),(0,1)}\{(1,0),(0,1)\} 로도 {(1,1),(1,−1)}\{(1,1),(1,-1)\} 로도 생성됩니다. 그러나 어떤 기저를 잡아도 벡터의 개수는 같다는 것이 알려져 있고, 그 개수를 VV 의 차원이라고 부릅니다. 차원이 잘 정의된 값이라는 사실이 "몇 차원을 쓰는가"라는 질문을 비로소 말이 되게 합니다.

e1=(1,0,…,0),…,en=(0,…,0,1)\mathbf{e}_1 = (1,0,\ldots,0), \ldots, \mathbf{e}_n = (0,\ldots,0,1) 을 표준기저라고 합니다. 배열에 적힌 숫자를 그대로 좌표로 읽을 때 우리가 말없이 쓰고 있던 기저가 이것입니다.

좌표 — 벡터가 아니라 기저에 붙는 값

여기가 이 글의 핵심입니다. [3, 1] 이라는 배열은 벡터 자체가 아니라 어떤 기저로 읽었을 때의 계수입니다.

정의. 기저 B={b1,…,bn}B = \{\mathbf{b}_1,\ldots,\mathbf{b}_n\} 에 대해 x=c1b1+⋯+cnbn\mathbf{x} = c_1\mathbf{b}_1 + \cdots + c_n\mathbf{b}_n 일 때 [x]B=(c1,…,cn)[\mathbf{x}]_B = (c_1,\ldots,c_n) 을 x\mathbf{x} 의 B-좌표라고 한다.

같은 점을 표준기저 격자로 읽으면 (3,1), 기저 B의 격자로 읽으면 (2,1)

기저 벡터를 열로 세운 행렬을 BB 라고 쓰면 정의가 곧바로 행렬 한 줄이 됩니다.

B [x]B=x⟹[x]B=B−1xB\,[\mathbf{x}]_B = \mathbf{x} \qquad \Longrightarrow \qquad [\mathbf{x}]_B = B^{-1}\mathbf{x}

BB 의 열이 기저이므로 독립이고, 그래서 BB 는 가역입니다. BB 는 좌표를 받아 벡터를 돌려주고, B−1B^{-1} 은 벡터를 받아 좌표를 돌려줍니다. 이 두 행렬을 좌표변환 행렬이라고 부릅니다.

손으로 한 번. b1=(1,1)\mathbf{b}_1 = (1,1), b2=(1,−1)\mathbf{b}_2 = (1,-1) 로 x=(3,1)\mathbf{x} = (3,1) 을 읽습니다.

B=(111−1),det⁡B=−2,B−1=1−2(−1−1−11)=(0.50.50.5−0.5)B = \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}, \qquad \det B = -2, \qquad B^{-1} = \frac{1}{-2}\begin{pmatrix} -1 & -1 \\ -1 & 1 \end{pmatrix} = \begin{pmatrix} 0.5 & 0.5 \\ 0.5 & -0.5 \end{pmatrix}

역행렬은 초급 44번에서 세운 2×2 공식을 그대로 쓴 것입니다. 그러면

[x]B=B−1x=(0.50.50.5−0.5)(31)=(21)[\mathbf{x}]_B = B^{-1}\mathbf{x} = \begin{pmatrix} 0.5 & 0.5 \\ 0.5 & -0.5 \end{pmatrix}\begin{pmatrix} 3 \\ 1 \end{pmatrix} = \begin{pmatrix} 2 \\ 1 \end{pmatrix}

되돌려 확인하면 2(1,1)+1(1,−1)=(3,1)2(1,1) + 1(1,-1) = (3,1) 입니다. 같은 점인데 배열이 [3, 1] 에서 [2, 1] 로 바뀌었습니다 — 점이 움직인 것이 아니라 눈금이 바뀐 것입니다.

기저가 정규직교이면 역행렬이 필요 없다

지난 글의 결과가 여기서 바로 쓰입니다. 기저가 정규직교이면 Q⊤Q=IQ^\top Q = I 라 Q−1=Q⊤Q^{-1} = Q^\top 이고, 좌표는 내적 한 번으로 나옵니다.

[x]Q=Q⊤x,즉ci=x⋅qi[\mathbf{x}]_Q = Q^\top\mathbf{x}, \qquad \text{즉} \quad c_i = \mathbf{x}\cdot\mathbf{q}_i

위의 b1,b2\mathbf{b}_1, \mathbf{b}_2 는 서로 직교하지만 노름이 2\sqrt{2} 라 정규직교는 아닙니다. 2\sqrt{2} 로 나눠 q1=12(1,1)\mathbf{q}_1 = \tfrac{1}{\sqrt2}(1,1), q2=12(1,−1)\mathbf{q}_2 = \tfrac{1}{\sqrt2}(1,-1) 로 만들면

c1=x⋅q1=3+12=22,c2=x⋅q2=3−12=2c_1 = \mathbf{x}\cdot\mathbf{q}_1 = \frac{3+1}{\sqrt2} = 2\sqrt2, \qquad c_2 = \mathbf{x}\cdot\mathbf{q}_2 = \frac{3-1}{\sqrt2} = \sqrt2

이고 22⋅12(1,1)+2⋅12(1,−1)=(2,2)+(1,−1)=(3,1)2\sqrt2 \cdot \tfrac{1}{\sqrt2}(1,1) + \sqrt2 \cdot \tfrac{1}{\sqrt2}(1,-1) = (2,2) + (1,-1) = (3,1) 로 맞습니다. 역행렬 하나가 전치 한 번으로 줄어드는 것이 정규직교기저를 선호하는 이유이고, 실제 구현이 직교기저를 고집하는 자리들이 전부 이 계산 때문입니다.

축을 바꾸는 조작은 전부 같은 것

기저를 바꾼다는 말이 낯설어 보이지만, AI 코드에서 이미 여러 이름으로 하고 있는 일입니다.

이름 새 기저로 무엇을 잡는가 무엇이 편해지는가
주성분 축 데이터의 분산이 큰 방향부터 앞쪽 몇 축만 남겨도 정보가 대부분 남는다
화이트닝 축마다 분산이 1이 되도록 잡은 축 모든 방향이 같은 규모가 된다
회전 위치 인코딩 2차원씩 각도만큼 돌린 축 상대 위치가 각도 차이로 읽힌다
대각화 행렬이 늘이기만 하는 방향 행렬 곱이 수 몇 개의 곱이 된다

네 줄이 전부 x↦B−1x\mathbf{x} \mapsto B^{-1}\mathbf{x} 한 줄이고, 다른 것은 BB 를 무엇으로 고르느냐뿐입니다. 표의 첫 두 줄이 필요로 하는 "분산이 큰 방향"과 넷째 줄의 "늘이기만 하는 방향"은 아직 정의하지 않았습니다 — 「중급 12번 · 고윳값과 고유벡터」가 그 방향을 정의하고, 「중급 14번 · 대칭 행렬」이 그것이 왜 늘 직교하는지를 답합니다. 셋째 줄은 「중급 11번 · 회전·전단·스케일」이 세우는 회전행렬을 씁니다.

행렬을 새 기저에서 다시 쓰는 규칙도 같은 자리에서 나옵니다. 원래 좌표에서 x↦Ax\mathbf{x} \mapsto A\mathbf{x} 인 조작을 B-좌표로 옮기면 "좌표를 벡터로 되돌리고 → AA 를 먹이고 → 다시 좌표로" 이므로

[Ax]B=B−1AB [x]B[A\mathbf{x}]_B = B^{-1}A B\,[\mathbf{x}]_B

B−1ABB^{-1}AB 라는 낯익은 꼴이 이 세 단계를 그대로 적은 것입니다. 논문에서 이 표현을 만나면 "같은 조작을 다른 축에서 본 것"이라고 읽으면 됩니다.

코드로 확인하기

import numpy as np

B = np.array([[1., 1], [1, -1]])      # 열이 기저 벡터
x = np.array([3., 1])

coords = np.linalg.solve(B, x)        # B c = x 를 푼다
print(coords)                         # [2. 1.]
print(B @ coords)                     # [3. 1.]  되돌아온다

정규직교로 바꾸면 풀 것이 없어집니다.

Q = B / np.sqrt(2)                    # 열의 노름을 1로
print(Q.T @ Q)                        # [[1 0] [0 1]]
print(Q.T @ x)                        # [2.828 1.414]  = (2√2, √2)

독립인지 세는 것은 matrix_rank 한 줄입니다.

V = np.array([[1., 2, 4],
              [2., 1, 5]])            # 열이 벡터 셋
print(np.linalg.matrix_rank(V))       # 2 — 셋이 만드는 것은 평면
print(np.linalg.matrix_rank(V[:, :2]))# 2 — 앞의 둘만으로 이미 평면

셋째 벡터가 span을 넓히지 않았다는 것이 숫자로 보입니다. 다만 이 한 줄을 그대로 믿기 전에 짚을 것이 있습니다.

다시 그 768차원으로

이제 첫머리의 질문을 정확히 쓸 수 있습니다. 문장 NN 개를 임베딩해 얻은 벡터 u1,…,uN∈R768\mathbf{u}_1,\ldots,\mathbf{u}_N \in \mathbb{R}^{768} 에 대해

모델이 실제로 쓰는 차원 수 =dim⁡span⁡{u1,…,uN}= \dim \operatorname{span}\{\mathbf{u}_1, \ldots, \mathbf{u}_N\}

이고, 이 값이 768보다 훨씬 작으면 그것이 표현 붕괴입니다.

3차원 상자 안에서 점들이 퍼져 있는 경우와 평면 하나에 붙어 있는 경우

그런데 이 정의를 컴퓨터로 그대로 확인할 수는 없습니다. 일차독립은 "cc 가 정확히 0인가"를 묻는데, 부동소수점에서 정확히 0인 값은 거의 나오지 않습니다. 실제 임베딩 행렬을 matrix_rank 에 넣으면 거의 언제나 768이 나옵니다 — 어느 방향으로도 10−710^{-7} 만큼은 퍼져 있기 때문입니다. 그러나 그 방향이 "쓰이고 있다"고 말하기는 어렵습니다.

그래서 실제로 필요한 것은 예/아니오가 아니라 방향마다 얼마나 쓰이는지를 재는 자이고, 그 자를 만드는 것이 「중급 15번 · 특잇값 분해와 랭크」입니다. 이 글은 무엇을 세야 하는지까지를 맡고, 어떻게 세는지는 그 글이 이어받습니다.

붕괴가 왜 문제인지도 이제 한 줄로 말할 수 있습니다. 벡터들이 낮은 차원 부분공간에 몰려 있으면 서로 다른 문장이 가리키는 방향의 폭이 좁아지고, 그러면 지난 글들에서 본 코사인 유사도가 전부 비슷한 값으로 밀려 순위가 뭉갭니다. 검색이 "가까운 것"을 못 고르는 것은 거리 지표의 문제가 아니라 span의 문제입니다.

정리

  • span은 선형결합 전체이고, 덧셈과 스칼라배에 닫혀 있어 부분공간이 됩니다. 벡터를 더 준다고 span이 반드시 커지지는 않습니다.
  • 일차독립은 ∑civi=0\sum c_i\mathbf{v}_i = \mathbf{0} 의 해가 자명한 것뿐이라는 조건이고, "하나도 나머지로 만들어지지 않는다"와 같은 말입니다. Rn\mathbb{R}^n 에서 n+1n+1 개는 항상 종속입니다.
  • 기저는 독립이면서 생성하는 모음이고, 그래서 표현이 유일합니다. 기저의 개수는 어떤 기저를 잡아도 같고 그것이 차원입니다.
  • 좌표는 기저에 붙는 값입니다. B[x]B=xB[\mathbf{x}]_B = \mathbf{x} 이므로 [x]B=B−1x[\mathbf{x}]_B = B^{-1}\mathbf{x} 이고, 기저가 정규직교이면 Q⊤xQ^\top\mathbf{x} 로 줄어듭니다.
  • 주성분·화이트닝·회전 위치 인코딩은 전부 BB 를 다르게 고른 같은 조작이며, 같은 사상을 새 축에서 보면 B−1ABB^{-1}AB 입니다.
  • "몇 차원을 쓰는가"는 span의 차원을 묻는 질문이지만, 부동소수점에서는 예/아니오로 판정되지 않아 방향마다 크기를 재는 자가 따로 필요합니다.

다음 글은 여기서 "조작"이라고 뭉뚱그린 AxA\mathbf{x} 를 제대로 봅니다 — 행렬은 숫자표가 아니라 함수이고, 그 열에는 기저가 어디로 갔는지가 적혀 있습니다.


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

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