AI공부 · 선형대수

3차원 선형 변환

5장. 3차원 선형 변환

이 장을 마치면 3×3 행렬을 “î, ĵ, k̂이 도착한 곳 세 개”로 읽고, 2차원에서 배운 행렬-벡터 곱과 행렬 곱을 그대로 3차원에 적용할 수 있습니다. 영상은 짧은 보충 편입니다.

5.1 왜 2차원 위주로 배우는가

3장과 4장은 2차원 벡터를 2차원 벡터로 보내는 변환만 다뤘습니다. 이 시리즈는 앞으로도 주로 2차원에서 설명합니다. 이유는 두 가지입니다.

  1. 화면에 그리기 쉽고 머릿속에 떠올리기 쉽습니다.
  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×24개
3차원î, ĵ, k̂3×39개
n차원n개n×nn²개

예제 — y축 둘레로 90° 회전. y축을 회전축(꼬치)으로 삼아 공간을 90° 돌립니다.

  • î는 z축 아래쪽 [0, 0, −1]로 갑니다
  • ĵ는 회전축 위에 있으니 움직이지 않습니다. [0, 1, 0] 그대로
  • k̂는 x축 [1, 0, 0]으로 갑니다
y축 둘레 90° 회전: ĵ는 그대로, î와 k̂는 자리를 옮긴다변환 전변환 후xyzxyzîĵk̂y축 둘레 90°î → [0, 0, −1]ĵ → [0, 1, 0]k̂ → [1, 0, 0]
y축 둘레 90° 회전 · 기저 세 개의 도착점

세 도착점을 열로 적으면 이 회전의 행렬입니다.

Ry = 001010−100   1열 = î 도착점, 2열 = ĵ 도착점, 3열 = k̂ 도착점

현장 비유. 문짝의 경첩(힌지)을 회전축으로 문을 90° 여는 것과 같습니다. 경첩 방향(세로축)은 그대로이고, 문의 나머지 두 방향만 자리를 바꿉니다. 회전축 위의 기저는 움직이지 않는다는 점을 기억하면 회전 행렬을 빠르게 쓸 수 있습니다.

5.4 3차원 행렬-벡터 곱

벡터 [x, y, z]가 어디로 가는지 구하는 추론은 2차원과 거의 똑같습니다.

  1. [x, y, z] = xî + yĵ + zk̂입니다. 각 좌표는 “기저를 몇 배 해서 더하라”는 지시입니다.
  2. 이 “늘이고 더하기” 관계는 변환 전에도 후에도 그대로입니다.
  3. 그러므로 도착점 = x × (1열) + y × (2열) + z × (3열).
abcdefghi x
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] 넣기.

Ry 1
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 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]
RzRy = 0−10001−100

순서를 바꾸면? RyRz(Rz 먼저)를 같은 방법으로 구하면 î → [0, 1, 0], ĵ → [0, 0, 1], k̂ → [1, 0, 0]입니다.

RyRz = 001100010  ≠  RzRy

3차원 회전은 순서에 특히 민감합니다. 책을 들고 직접 해보면 바로 확인됩니다. “세로축으로 90° 돌리고 가로축으로 90° 돌리기”와 그 반대 순서는 책의 최종 자세가 다릅니다.

계산량. 곱의 각 열은 3차원 행렬-벡터 곱 한 번(곱셈 9번)이고 열이 3개이므로, 3×3 행렬 곱에는 곱셈이 27번 필요합니다. 4장의 “n×n 행렬 곱은 약 n³번”과 맞아떨어집니다.

5.6 그래픽스·로보틱스·AI와의 연결

복잡한 회전을 쉬운 회전의 합성으로. 3차원에서 임의의 방향으로 회전한 자세는 말로 설명하기 어렵습니다. 그래서 컴퓨터 그래픽스와 로보틱스에서는 회전을 축 하나씩 돌리는 쉬운 회전들의 합성으로 나눠 생각하고, 행렬 곱으로 합칩니다.

  • 드론·비행기의 자세: 롤(앞뒤 축), 피치(좌우 축), 요(수직 축) 세 회전의 곱으로 표현합니다. 비행 제어기와 짐벌은 이 3×3 회전 행렬을 매 순간 계산합니다. 곱하는 순서(예: 요 → 피치 → 롤)를 틀리면 자세가 달라집니다.
  • 로봇 팔: 관절마다 회전 행렬이 있고, 어깨 → 팔꿈치 → 손목 행렬을 차례로 곱하면 손끝의 방향이 나옵니다.
  • 3D 화면: 게임·모델링 프로그램은 물체 회전, 카메라 방향을 행렬 곱으로 합친 뒤 수백만 개 꼭짓점에 한 번에 곱합니다.

AI에서는

  1. 3D 비전. 카메라 자세 추정, 포인트 클라우드 정합, NeRF·3D Gaussian Splatting(연세대 “3D 컴퓨터비전” 과목)은 전부 카메라의 3×3 회전 행렬을 다룹니다.
  2. 더 높은 차원으로. 이 장의 핵심 메시지는 “차원이 늘어도 규칙은 같다”입니다. LLM의 4096차원 벡터에 4096×4096 행렬을 곱하는 것도 “기저 4096개의 도착점을 열로 담은 표”에 벡터를 넣는 것입니다.
  3. 입력과 출력 차원이 다르면 (미리 보기). 행렬의 열 개수 = 입력 차원(기저가 몇 개인가), 열 하나의 길이 = 출력 차원(도착점이 몇 차원인가)입니다. 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-hatz 방향 단위벡터 [0, 0, 1]
3×3 행렬3×3 matrixî, ĵ, k̂의 도착점을 열로 담은 숫자 9개
회전축axis of rotation회전해도 움직이지 않는 직선
롤·피치·요roll, pitch, yaw앞뒤·좌우·수직 축 둘레 회전. 기체 자세 표현
평면나라flatland영상에서 2차원 세계를 부르는 말

5.9 연습문제

기본

  1. î → [2, 0, 0], ĵ → [0, 3, 0], k̂ → [0, 0, 1]인 변환의 행렬을 쓰고, [1, 1, 1]이 어디로 가는지 구하라.
  2. 5.3의 Ry로 [0, 0, 2]는 어디로 가는가?
  3. x축 둘레 90° 회전(y축이 z축 쪽으로 가는 방향)의 행렬을 구하라.
  4. 행렬 [[1, 0, 0], [0, 1, 0], [0, 0, 0]]은 공간을 어떻게 바꾸는가? [3, 4, 5]는 어디로 가는가?

응용

  1. Ry를 두 번 하면 어떤 행렬이 되는가? 어떤 변환인가?
  2. 열이 [1, 0, 0], [0, 1, 0], [1, 1, 0]인 행렬은 공간을 어떻게 변형하는가? [1, 1, −1]은 어디로 가는가?
  3. 3차원 벡터를 2차원 벡터로 보내는 선형 변환의 행렬은 크기가 몇×몇인가? 이유를 “열 = 기저의 도착점”으로 설명하라.
  4. 3×3 행렬 두 개를 곱할 때 곱셈은 모두 몇 번 필요한가?
해설 펼치기
  1. [[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배로 늘이는 변환입니다.
  2. 0·(1열) + 0·(2열) + 2·[1, 0, 0] = [2, 0, 0]. 위를 가리키던 벡터가 x축으로 누웠습니다.
  3. î는 회전축 위라 그대로 [1, 0, 0]. ĵ → [0, 0, 1], k̂ → [0, −1, 0]. 열로 적으면 [[1, 0, 0], [0, 0, −1], [0, 1, 0]](행 단위 표기).
  4. k̂가 원점(영벡터)으로 갑니다. 3차원 공간 전체가 xy 평면으로 납작하게 눌립니다. [3, 4, 5] → [3, 4, 0]. 높이 정보가 사라집니다. 바닥에 생기는 그림자(투영)와 같습니다.
  5. î → [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° 회전입니다.
  6. 3열 [1, 1, 0] = 1열 + 2열이라 세 도착점이 선형 종속입니다. 스팬이 xy 평면뿐이므로 공간 전체가 평면 하나로 눌립니다. [1, 1, −1] → [1, 0, 0] + [0, 1, 0] − [1, 1, 0] = [0, 0, 0]. 원점으로 사라지는 벡터도 생깁니다. (이 성질은 뒤의 행렬식·영공간 장에서 다룹니다.)
  7. 2×3. 입력 기저가 3개(î, ĵ, k̂)이니 열이 3개이고, 각 도착점이 2차원이니 열 하나의 길이가 2입니다. “출력 차원 × 입력 차원”으로 기억합니다.
  8. 곱의 열 하나 = 행렬-벡터 곱 한 번 = 곱셈 9번. 열이 3개라 27번(= 3³).

다음 장 예고: 6장 “행렬식”. 변환이 넓이(부피)를 몇 배로 늘리거나 줄이는지를 나타내는 숫자를 다룹니다. 6번 문제처럼 공간이 눌리는 경우가 왜 특별한지도 여기서 나옵니다.