AI공부 · 선형대수
3차원 선형 변환
5장. 3차원 선형 변환
이 장을 마치면 3×3 행렬을 “î, ĵ, k̂이 도착한 곳 세 개”로 읽고, 2차원에서 배운 행렬-벡터 곱과 행렬 곱을 그대로 3차원에 적용할 수 있습니다. 영상은 짧은 보충 편입니다.
5.1 왜 2차원 위주로 배우는가
3장과 4장은 2차원 벡터를 2차원 벡터로 보내는 변환만 다뤘습니다. 이 시리즈는 앞으로도 주로 2차원에서 설명합니다. 이유는 두 가지입니다.
- 화면에 그리기 쉽고 머릿속에 떠올리기 쉽습니다.
- 더 중요한 이유로, 2차원에서 핵심 아이디어를 잡으면 더 높은 차원으로 거의 그대로 넘어갑니다.
그래도 가끔은 평면 밖으로 고개를 내밀어 같은 생각이 3차원에서도 통하는지 확인해두는 것이 좋습니다. 이 장이 그 확인입니다. 새 개념은 거의 없고, 3장·4장을 다시 한 번 다지는 장입니다.
5.2 3차원 선형 변환과 세 번째 기저 k̂
3차원 벡터를 받아 3차원 벡터를 돌려주는 선형 변환을 생각합시다. 2차원과 같은 방식으로 상상합니다.
- 3차원 공간을 입체 격자(정육면체 블록을 쌓은 모양)로 채웁니다
- 격자선이 평행하고 간격이 균일하게, 원점은 제자리에 있도록 공간 전체를 주물러 움직입니다
- 움직이는 점 하나하나는 그 점을 끝으로 하는 벡터를 대신합니다
그리고 2차원과 똑같이, 변환은 기저 벡터가 어디로 가는가만으로 완전히 정해집니다. 달라진 점은 기저가 세 개라는 것뿐입니다.
| 기호 | 읽는 법 | 방향 | 좌표 |
|---|---|---|---|
| î | 아이 햇 | x 방향 | [1, 0, 0] |
| ĵ | 제이 햇 | y 방향 | [0, 1, 0] |
| k̂ | 케이 햇 | z 방향 (새로 추가) | [0, 0, 1] |
3차원 격자 전체를 그리면 화면이 금방 복잡해집니다. 그래서 3차원에서는 기저 벡터 세 개만 따라가는 것이 오히려 이해하기 쉽습니다. 원래 축을 배경에 남겨두고, 세 화살표가 어느 좌표에 도착하는지만 읽습니다.
5.3 3×3 행렬
세 기저 벡터가 도착한 좌표를 각각 열로 적으면 3×3 행렬이 됩니다. 도착점 하나에 숫자 3개, 기저가 3개이므로 숫자 9개가 3차원 선형 변환을 완전히 기록합니다.
| 차원 | 기저 벡터 | 행렬 크기 | 기록에 필요한 숫자 |
|---|---|---|---|
| 2차원 | î, ĵ | 2×2 | 4개 |
| 3차원 | î, ĵ, k̂ | 3×3 | 9개 |
| n차원 | n개 | n×n | n²개 |
예제 — y축 둘레로 90° 회전. y축을 회전축(꼬치)으로 삼아 공간을 90° 돌립니다.
- î는 z축 아래쪽 [0, 0, −1]로 갑니다
- ĵ는 회전축 위에 있으니 움직이지 않습니다. [0, 1, 0] 그대로
- k̂는 x축 [1, 0, 0]으로 갑니다
세 도착점을 열로 적으면 이 회전의 행렬입니다.
현장 비유. 문짝의 경첩(힌지)을 회전축으로 문을 90° 여는 것과 같습니다. 경첩 방향(세로축)은 그대로이고, 문의 나머지 두 방향만 자리를 바꿉니다. 회전축 위의 기저는 움직이지 않는다는 점을 기억하면 회전 행렬을 빠르게 쓸 수 있습니다.
5.4 3차원 행렬-벡터 곱
벡터 [x, y, z]가 어디로 가는지 구하는 추론은 2차원과 거의 똑같습니다.
- [x, y, z] = xî + yĵ + zk̂입니다. 각 좌표는 “기저를 몇 배 해서 더하라”는 지시입니다.
- 이 “늘이고 더하기” 관계는 변환 전에도 후에도 그대로입니다.
- 그러므로 도착점 = x × (1열) + y × (2열) + z × (3열).
y
z = x a
d
g + y b
e
h + z c
f
i = ax + by + cz
dx + ey + fz
gx + hy + iz
예제 1 — y축 회전에 [1, 2, 3] 넣기.
2
3 = 1 0
0
−1 + 2 0
1
0 + 3 1
0
0 = 3
2
−1
y 좌표 2는 회전축 방향이라 그대로 남고, x와 z 성분만 자리를 바꿨습니다.
예제 2 — 일반적인 행렬. M = [[1, 0, 2], [−1, 3, 1], [0, 1, 1]]에 [1, 1, 2]를 넣습니다. M의 열은 [1, −1, 0], [0, 3, 1], [2, 1, 1]입니다.
−1
0 + 1 0
3
1 + 2 2
1
1 = 5
4
3
열이 하나 늘었을 뿐, 하는 일은 3장과 같습니다. 열 = 기저의 도착점, 행렬 × 벡터 = 열들의 선형결합.
5.5 3×3 행렬 곱 = 회전의 합성
3×3 행렬 두 개의 곱도 뜻은 4장과 같습니다. 오른쪽 행렬의 변환을 먼저 하고, 그다음 왼쪽 행렬의 변환을 합니다. 계산도 같습니다. î, ĵ, k̂ 세 개를 차례로 따라갑니다.
곱의 k번째 열 = 왼쪽 행렬 × (오른쪽 행렬의 k번째 열). 열이 3개로 늘었을 뿐입니다.
예제 — y축 회전 후 z축 회전. z축 둘레 90° 회전 Rz는 k̂를 고정하고, î를 [0, 1, 0]으로, ĵ를 [−1, 0, 0]으로 보냅니다. (위에서 내려다본 xy 평면이 3장의 90° 회전처럼 돕니다.) “Ry 먼저, 그다음 Rz” = RzRy를 î, ĵ, k̂를 따라가 구합니다.
| 기저 | R_y 후 | R_z 후 (최종) |
|---|---|---|
| î | [0, 0, −1] | [0, 0, −1] (z축 위라 안 움직임) |
| ĵ | [0, 1, 0] | [−1, 0, 0] |
| k̂ | [1, 0, 0] | [0, 1, 0] |
순서를 바꾸면? RyRz(Rz 먼저)를 같은 방법으로 구하면 î → [0, 1, 0], ĵ → [0, 0, 1], k̂ → [1, 0, 0]입니다.
3차원 회전은 순서에 특히 민감합니다. 책을 들고 직접 해보면 바로 확인됩니다. “세로축으로 90° 돌리고 가로축으로 90° 돌리기”와 그 반대 순서는 책의 최종 자세가 다릅니다.
계산량. 곱의 각 열은 3차원 행렬-벡터 곱 한 번(곱셈 9번)이고 열이 3개이므로, 3×3 행렬 곱에는 곱셈이 27번 필요합니다. 4장의 “n×n 행렬 곱은 약 n³번”과 맞아떨어집니다.
5.6 그래픽스·로보틱스·AI와의 연결
복잡한 회전을 쉬운 회전의 합성으로. 3차원에서 임의의 방향으로 회전한 자세는 말로 설명하기 어렵습니다. 그래서 컴퓨터 그래픽스와 로보틱스에서는 회전을 축 하나씩 돌리는 쉬운 회전들의 합성으로 나눠 생각하고, 행렬 곱으로 합칩니다.
- 드론·비행기의 자세: 롤(앞뒤 축), 피치(좌우 축), 요(수직 축) 세 회전의 곱으로 표현합니다. 비행 제어기와 짐벌은 이 3×3 회전 행렬을 매 순간 계산합니다. 곱하는 순서(예: 요 → 피치 → 롤)를 틀리면 자세가 달라집니다.
- 로봇 팔: 관절마다 회전 행렬이 있고, 어깨 → 팔꿈치 → 손목 행렬을 차례로 곱하면 손끝의 방향이 나옵니다.
- 3D 화면: 게임·모델링 프로그램은 물체 회전, 카메라 방향을 행렬 곱으로 합친 뒤 수백만 개 꼭짓점에 한 번에 곱합니다.
AI에서는
- 3D 비전. 카메라 자세 추정, 포인트 클라우드 정합, NeRF·3D Gaussian Splatting(연세대 “3D 컴퓨터비전” 과목)은 전부 카메라의 3×3 회전 행렬을 다룹니다.
- 더 높은 차원으로. 이 장의 핵심 메시지는 “차원이 늘어도 규칙은 같다”입니다. LLM의 4096차원 벡터에 4096×4096 행렬을 곱하는 것도 “기저 4096개의 도착점을 열로 담은 표”에 벡터를 넣는 것입니다.
- 입력과 출력 차원이 다르면 (미리 보기). 행렬의 열 개수 = 입력 차원(기저가 몇 개인가), 열 하나의 길이 = 출력 차원(도착점이 몇 차원인가)입니다. PyTorch
nn.Linear(512, 256)의 가중치 행렬이 256×512인 이유입니다. 입력 기저 512개 각각이 256차원 어딘가에 도착합니다. 이 내용은 시리즈 뒤쪽(비정사각 행렬)에서 다시 나옵니다.
5.7 코드 실습
실습 1 — 축별 회전 행렬 만들기
각도 t(라디안)만큼 각 축 둘레로 돌리는 행렬입니다. 90°를 넣으면 본문의 행렬이 나오는지 확인합니다.
import numpy as np
def Rx(t):
c, s = np.cos(t), np.sin(t)
return np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
def Ry(t):
c, s = np.cos(t), np.sin(t)
return np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
def Rz(t):
c, s = np.cos(t), np.sin(t)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
q = np.pi / 2 # 90°
print(np.round(Ry(q))) # [[ 0 0 1] [ 0 1 0] [-1 0 0]] 5.3의 R_y
print(np.round(Ry(q) @ [1, 2, 3])) # [ 3 2 -1] 5.4 예제 1
실습 2 — 순서에 따라 달라지는 3차원 회전
A = Rz(q) @ Ry(q) # R_y 먼저, 그다음 R_z
B = Ry(q) @ Rz(q) # R_z 먼저, 그다음 R_y
print(np.round(A)) # [[ 0 -1 0] [ 0 0 1] [-1 0 0]]
print(np.round(B)) # [[ 0 0 1] [ 1 0 0] [ 0 1 0]]
print(np.allclose(A, B)) # False
# 드론 자세: 요(30°) → 피치(10°) → 롤(5°)
d = np.deg2rad
R_drone = Rz(d(30)) @ Ry(d(10)) @ Rx(d(5))
print(np.round(R_drone @ [1, 0, 0], 3)) # 기체 앞쪽(x)이 가리키는 방향
실습 3 — 3차원으로 그려보기
흐린 화살표는 원래 기저, 진한 화살표는 도착한 기저입니다. 마우스로 돌려가며 봅니다(Jupyter에서는 맨 위에 %matplotlib widget).
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'AppleGothic'
def draw3d(M, title):
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(projection='3d')
colors, names = ['tab:orange', 'tab:green', 'tab:blue'], ['î', 'ĵ', 'k̂']
for k in range(3):
e = np.eye(3)[:, k] # k번째 기저
ax.quiver(0, 0, 0, *e, color=colors[k], alpha=0.25)
ax.quiver(0, 0, 0, *(M @ e), color=colors[k],
label=f'{names[k]} → {np.round(M @ e, 2)}')
for set_lim in (ax.set_xlim, ax.set_ylim, ax.set_zlim):
set_lim(-1.2, 1.2)
ax.set_xlabel('x'); ax.set_ylabel('y'); ax.set_zlabel('z')
ax.set_title(title); ax.legend(); plt.show()
draw3d(Ry(q), 'y축 둘레 90°')
draw3d(Rz(q) @ Ry(q), 'R_y 먼저, 그다음 R_z')
draw3d(Ry(q) @ Rz(q), 'R_z 먼저, 그다음 R_y')
5.8 용어 정리
| 한국어 | 영어 | 뜻 |
|---|---|---|
| k̂ (케이 햇) | k-hat | z 방향 단위벡터 [0, 0, 1] |
| 3×3 행렬 | 3×3 matrix | î, ĵ, k̂의 도착점을 열로 담은 숫자 9개 |
| 회전축 | axis of rotation | 회전해도 움직이지 않는 직선 |
| 롤·피치·요 | roll, pitch, yaw | 앞뒤·좌우·수직 축 둘레 회전. 기체 자세 표현 |
| 평면나라 | flatland | 영상에서 2차원 세계를 부르는 말 |
5.9 연습문제
기본
- î → [2, 0, 0], ĵ → [0, 3, 0], k̂ → [0, 0, 1]인 변환의 행렬을 쓰고, [1, 1, 1]이 어디로 가는지 구하라.
- 5.3의 Ry로 [0, 0, 2]는 어디로 가는가?
- x축 둘레 90° 회전(y축이 z축 쪽으로 가는 방향)의 행렬을 구하라.
- 행렬 [[1, 0, 0], [0, 1, 0], [0, 0, 0]]은 공간을 어떻게 바꾸는가? [3, 4, 5]는 어디로 가는가?
응용
- Ry를 두 번 하면 어떤 행렬이 되는가? 어떤 변환인가?
- 열이 [1, 0, 0], [0, 1, 0], [1, 1, 0]인 행렬은 공간을 어떻게 변형하는가? [1, 1, −1]은 어디로 가는가?
- 3차원 벡터를 2차원 벡터로 보내는 선형 변환의 행렬은 크기가 몇×몇인가? 이유를 “열 = 기저의 도착점”으로 설명하라.
- 3×3 행렬 두 개를 곱할 때 곱셈은 모두 몇 번 필요한가?
해설 펼치기
- [[2, 0, 0], [0, 3, 0], [0, 0, 1]]. [1, 1, 1] → [2, 0, 0] + [0, 3, 0] + [0, 0, 1] = [2, 3, 1]. x는 2배, y는 3배로 늘이는 변환입니다.
- 0·(1열) + 0·(2열) + 2·[1, 0, 0] = [2, 0, 0]. 위를 가리키던 벡터가 x축으로 누웠습니다.
- î는 회전축 위라 그대로 [1, 0, 0]. ĵ → [0, 0, 1], k̂ → [0, −1, 0]. 열로 적으면 [[1, 0, 0], [0, 0, −1], [0, 1, 0]](행 단위 표기).
- k̂가 원점(영벡터)으로 갑니다. 3차원 공간 전체가 xy 평면으로 납작하게 눌립니다. [3, 4, 5] → [3, 4, 0]. 높이 정보가 사라집니다. 바닥에 생기는 그림자(투영)와 같습니다.
- î → [0, 0, −1] → Ry가 −k̂를 −[1, 0, 0]으로 보내 [−1, 0, 0]. ĵ → [0, 1, 0]. k̂ → [1, 0, 0] → [0, 0, −1]. [[−1, 0, 0], [0, 1, 0], [0, 0, −1]]. y축 둘레 180° 회전입니다.
- 3열 [1, 1, 0] = 1열 + 2열이라 세 도착점이 선형 종속입니다. 스팬이 xy 평면뿐이므로 공간 전체가 평면 하나로 눌립니다. [1, 1, −1] → [1, 0, 0] + [0, 1, 0] − [1, 1, 0] = [0, 0, 0]. 원점으로 사라지는 벡터도 생깁니다. (이 성질은 뒤의 행렬식·영공간 장에서 다룹니다.)
- 2×3. 입력 기저가 3개(î, ĵ, k̂)이니 열이 3개이고, 각 도착점이 2차원이니 열 하나의 길이가 2입니다. “출력 차원 × 입력 차원”으로 기억합니다.
- 곱의 열 하나 = 행렬-벡터 곱 한 번 = 곱셈 9번. 열이 3개라 27번(= 3³).
다음 장 예고: 6장 “행렬식”. 변환이 넓이(부피)를 몇 배로 늘리거나 줄이는지를 나타내는 숫자를 다룹니다. 6번 문제처럼 공간이 눌리는 경우가 왜 특별한지도 여기서 나옵니다.