AI공부 · 선형대수

행렬 곱과 합성 변환

4장. 행렬 곱과 합성 변환

이 장을 마치면 행렬 곱을 “변환을 연달아 적용하는 것”으로 읽고, 외운 공식 없이 î와 ĵ를 따라가 곱을 계산하며, 곱하는 순서가 왜 중요한지 그림으로 설명할 수 있습니다.

4.1 3장 핵심 복습

이 장은 3장 위에 그대로 쌓입니다. 아래 네 줄이 낯설면 3장을 먼저 다시 보세요.

  1. 선형 변환은 원점을 고정하고 격자선을 평행·등간격으로 유지하며 공간을 움직이는 것입니다.
  2. 그 변환은 î와 ĵ가 어디에 도착하는가만으로 완전히 정해집니다.
  3. [x, y] = xî + yĵ이므로, 변환 후에는 x × (도착한 î) + y × (도착한 ĵ)에 갑니다.
  4. 도착한 î와 ĵ를 열로 적은 것이 행렬이고, 위 계산이 행렬-벡터 곱입니다.

즉 행렬 하나 = 선형 변환 하나이고, 행렬을 벡터에 곱하는 것은 그 변환을 벡터에 적용하는 것입니다.

4.2 합성 변환: 회전 후 전단

변환을 하나 하고 나서 또 다른 변환을 이어서 하고 싶을 때가 많습니다. 예를 들어 평면을 반시계 방향으로 90° 돌린 다음, 3장의 전단(밀기)을 적용한다고 합시다.

변환행렬 (행 단위)î 도착ĵ 도착
회전 R (반시계 90°)[[0, −1], [1, 0]][0, 1][−1, 0]
전단 S[[1, 1], [0, 1]][1, 0][1, 1]

처음부터 끝까지의 전체 효과도 하나의 선형 변환입니다. 두 변환 모두 격자선을 평행·등간격으로 유지하고 원점을 고정하니, 연달아 해도 그 성질이 유지되기 때문입니다. 이렇게 두 변환을 이어 붙인 것을 합성 변환(composition)이라고 합니다. 회전과도 다르고 전단과도 다른 새 변환입니다.

새 변환이니 자기만의 행렬이 있습니다. 구하는 방법은 3장과 같습니다. î와 ĵ를 끝까지 따라갑니다.

  • î의 여정: [1, 0] → 회전 후 [0, 1] → 전단 후 [1, 1]
  • ĵ의 여정: [0, 1] → 회전 후 [−1, 0] → 전단 후 [−1, 0]

전단은 x축 위의 점을 움직이지 않으니 [−1, 0]은 그대로입니다. 최종 도착점을 열로 적으면 합성 행렬이 됩니다.

회전 후 전단 = 1−110

이 행렬 하나가 “회전하고 나서 전단”하는 두 단계 작업을 한 번의 동작으로 담습니다.

현장 비유. 자재를 “90° 돌려 놓고, 다시 비스듬히 밀어 설치”하는 공정을 두 작업자가 나눠 하던 것을, 두 공정을 합친 지그(치구) 하나로 한 번에 처리하는 것과 같습니다.

4.3 합성 = 행렬 곱, 오른쪽에서 왼쪽으로 읽기

어떤 벡터 v를 회전하고 나서 전단하는 결과를 구하는 방법은 두 가지입니다.

  1. 돌아가는 길: v에 회전 행렬을 곱하고, 그 결과에 다시 전단 행렬을 곱합니다. 식으로는 S(Rv).
  2. 지름길: 4.2에서 구한 합성 행렬을 v에 한 번 곱합니다.

합성 행렬은 “회전 후 전단”과 정확히 같은 효과를 내도록 만들었으니, 어떤 v를 넣어도 두 방법의 답은 같습니다. 그래서 합성 행렬을 두 행렬의 곱이라고 부르고 이렇게 씁니다.

1101전단 S 0−110회전 R = 1−110합성 SR    S(Rv) = (SR)v

행렬 곱의 기하학적 의미 = 한 변환을 적용한 뒤 다른 변환을 적용하는 것. 숫자 숲에서 길을 잃지 않도록 이 문장을 항상 기억하세요.

주의: 오른쪽에서 왼쪽으로 읽습니다. 위 식에서 먼저 일어난 회전 R이 오른쪽에, 나중에 일어난 전단 S가 왼쪽에 있습니다. 함수 표기법 때문입니다. f(x)처럼 함수를 변수 왼쪽에 쓰다 보니, g(f(x))는 f를 먼저, g를 나중에 합니다. 행렬도 벡터 옆에 가까운 것부터 적용됩니다.

식읽는 법
SRR을 먼저, 그다음 S
RSS를 먼저, 그다음 R
ABCC → B → A 순서

4.4 숫자만으로 곱 구하기

4.2에서는 그림(애니메이션)을 보고 도착점을 읽었습니다. 이번에는 그림 없이 행렬의 숫자만으로 합성 행렬을 구해봅시다.

M1 = 1−210    M2 = 0210    M₁ 먼저, 그다음 M₂ = M2M1

1단계 — î 따라가기. M₁을 적용한 뒤 î의 위치는 정의상 M₁의 1열 [1, 1]입니다. 계산할 것도 없습니다. 그 벡터에 M₂를 적용합니다. 3장 방식대로 M₂의 열을 1배, 1배 해서 더합니다.

M2 1
1
= 1 0
1
+ 1 2
0
= 2
1

이것이 합성 행렬의 1열입니다.

2단계 — ĵ 따라가기. M₁을 적용한 뒤 ĵ는 M₁의 2열 [−2, 0]에 있습니다. 그 벡터에 M₂를 적용합니다.

M2 −2
0
= −2 0
1
+ 0 2
0
= 0
−2

이것이 합성 행렬의 2열입니다.

î와 ĵ를 두 번 따라가면 합성 행렬의 열이 나온다시작M₁ 적용 후M₂ 적용 후M₁ = 1열M₂ × [1, 1]M₁ = 2열M₂ × [−2, 0]î = [1, 0][1, 1][2, 1]ĵ = [0, 1][−2, 0][0, −2]M₂M₁ = 1열 [2, 1], 2열 [0, −2]최종 도착점 두 개를 열로 적는다
M₂M₁ 계산 · î와 ĵ가 두 변환을 차례로 거치는 경로

결과

M2M1 = 0210 1−210 = 201−2

핵심은 이것입니다. 오른쪽 행렬의 열은 중간 도착점이고, 거기에 왼쪽 행렬을 곱한 것이 최종 도착점입니다. 새로 외울 것은 없습니다. 3장의 행렬-벡터 곱을 두 번 했을 뿐입니다.

4.5 일반 공식: 열 단위로 이해하기

4.4의 추론은 어떤 행렬에도 똑같이 통합니다. 왼쪽 행렬을 [[a, b], [c, d]], 오른쪽 행렬을 [[e, f], [g, h]]라 합시다.

1열 구하기. î는 먼저 오른쪽 행렬의 1열 [e, g]에 도착합니다. 거기에 왼쪽 행렬을 적용하면 최종 도착점이 됩니다.

abcd e
g
= e a
c
+ g b
d
= ae + bg
ce + dg

2열 구하기. ĵ는 먼저 오른쪽 행렬의 2열 [f, h]에 도착합니다.

abcd f
h
= f a
c
+ h b
d
= af + bh
cf + dh

합치면

abcd efgh = ae + bgaf + bhce + dgcf + dh

곱의 k번째 열 = 왼쪽 행렬 × (오른쪽 행렬의 k번째 열).

외우는 방식과 비교. 학교에서는 보통 “왼쪽의 i행과 오른쪽의 j열을 같은 자리끼리 곱해 더하면 (i, j) 성분”이라고 가르칩니다. 예를 들어 1행 [a, b]와 1열 [e, g]로 ae + bg. 결과는 정확히 같습니다.

방식장점단점
열 단위 (변환 따라가기)왜 그런지 설명되고, 성질을 그림으로 추론할 수 있다손계산이 조금 길다
행×열 성분 공식손으로 빠르게 계산한다의미가 보이지 않아 순서·성질을 자주 헷갈린다

손계산은 행×열로 해도 좋습니다. 다만 외우기 전에 “행렬 곱 = 변환을 연달아 적용하기”라는 뜻부터 익숙해져야 합니다. 다음 두 절이 그 이유입니다.

4.6 순서가 중요하다

숫자 곱셈은 2 × 3 = 3 × 2처럼 순서를 바꿔도 같습니다. 행렬 곱셈은 순서를 바꾸면 대개 달라집니다. 계산하지 않고 그림만 상상해도 알 수 있습니다.

전단 S(î는 그대로, ĵ는 오른쪽으로 밀림)과 90° 회전 R을 순서만 바꿔 해봅시다.

순서식î 최종 도착ĵ 최종 도착모습
전단 먼저, 그다음 회전RS[0, 1][−1, 1]두 벡터가 가까이 모여 비슷한 쪽을 가리킴
회전 먼저, 그다음 전단SR[1, 1][−1, 0]두 벡터가 더 벌어져 다른 쪽을 가리킴
RS = 0−111  ≠  SR = 1−110

머릿속으로 확인하기. 전단을 먼저 하면 ĵ가 [1, 1]로 기울어 î와 45°가 됩니다. 이 둘을 통째로 90° 돌려도 사이각 45°는 유지됩니다. 반대로 회전을 먼저 하면 ĵ가 x축 위 [−1, 0]으로 가고, x축 위의 점은 전단에서 움직이지 않습니다. 반면 î는 [0, 1]에서 [1, 1]로 밀려 두 벡터 사이각이 135°로 벌어집니다. 행렬 곱셈 없이도 결과가 다르다는 걸 알 수 있습니다.

현장 비유. “벽지를 바르고 몰딩을 단다”와 “몰딩을 달고 벽지를 바른다”가 다른 결과를 내듯, 공정 순서가 바뀌면 결과물이 달라집니다. 그래서 AB와 BA는 일반적으로 다릅니다. 이를 “행렬 곱셈은 교환법칙이 성립하지 않는다”고 합니다.

가끔 순서를 바꿔도 같은 경우도 있습니다. 예를 들어 회전끼리(30° 돌리고 60° 돌리기)나, 모든 방향으로 같은 배율로 키우는 확대는 순서와 상관없습니다. 하지만 이것은 예외입니다.

4.7 결합법칙이 당연한 이유

행렬 세 개 A, B, C를 곱할 때, 괄호를 어디에 치든 결과가 같습니다. 이것을 결합법칙이라고 합니다.

(AB)C = A(BC)

숫자로 증명하려면 문자 12개를 펼쳐서 양변을 전부 전개해야 합니다. 고되고, 왜 그런지도 알려주지 않습니다.

변환으로 생각하면 증명할 것이 없습니다. 오른쪽에서 왼쪽으로 읽으면 양쪽 모두 C를 하고, 그다음 B를 하고, 그다음 A를 한다는 뜻입니다.

  • (AB)C: C를 하고, 그다음 “B 후 A”를 묶어서 한다
  • A(BC): “C 후 B”를 묶어서 하고, 그다음 A를 한다

같은 세 동작을 같은 순서로 하니 결과가 다를 수가 없습니다. 묶는 방식(괄호)만 다를 뿐입니다. 이건 속임수가 아니라 엄연한 증명이고, 더 나아가 왜 그 성질이 참인지 설명해주는 좋은 증명입니다.

성질행렬 곱에서변환으로 본 이유
교환법칙 AB = BA대개 성립하지 않음동작 순서를 바꾸면 결과가 달라진다
결합법칙 (AB)C = A(BC)항상 성립같은 동작을 같은 순서로 하고 묶음만 다르다

4.8 AI와의 연결

  1. 층을 쌓는 것 = 변환의 합성. 신경망은 층을 차례로 통과합니다. 입력 x가 첫 층 W₁을 거치고 다음 층 W₂를 거치면 W₂(W₁x)입니다. 이 장에서 본 “오른쪽부터 적용”과 똑같습니다.
  2. 비선형 함수가 꼭 필요한 이유. 활성함수 없이 선형 층만 쌓으면 W₂(W₁x) = (W₂W₁)x가 됩니다. 층을 100개 쌓아도 결국 행렬 하나, 즉 층 하나와 같습니다. 3장에서 “사이사이에 비선형을 넣는다”고 한 이유가 바로 이것입니다.
  3. 결합법칙과 계산 속도. A(Bx)와 (AB)x는 답이 같지만 계산량은 크게 다릅니다. n×n 행렬이면 행렬-벡터 곱은 약 n²번, 행렬-행렬 곱은 약 n³번 곱셈이 필요합니다. n = 4096이면 A(Bx)는 약 3,400만 번, (AB)x는 약 690억 번입니다. 그래서 신경망 추론은 항상 벡터에 한 층씩 차례로 곱합니다. 괄호 위치를 고르는 것이 곧 성능 최적화입니다.
  4. LoRA 미리 보기. LLM 파인튜닝 기법 LoRA는 큰 가중치 행렬의 변화량을 작은 행렬 두 개의 곱 BA로 표현합니다. 변환으로 보면 “아주 좁은 공간(차원 8~64)으로 보냈다가 다시 크게 펼치는” 두 단계 합성입니다. 학습할 숫자가 수백 분의 1로 줄어듭니다.
  5. 순서가 중요한 실제 사례. 비전AI 데이터 증강에서 “회전 후 확대”와 “확대 후 회전”은 같은 배율이면 같지만, “회전 후 가로만 늘리기”와 “가로만 늘린 후 회전”은 다른 사진이 됩니다. 3D 그래픽스의 “모델 → 카메라 → 화면” 변환도 행렬을 정해진 순서로 곱해야 합니다.

4.9 코드 실습

실습 1 — 순서에 따라 곱이 달라지는지 확인

import numpy as np

R = np.array([[0, -1], [1, 0]])   # 반시계 90° 회전
S = np.array([[1, 1], [0, 1]])    # 전단

print(S @ R)   # [[ 1 -1] [ 1  0]]  회전 먼저, 그다음 전단
print(R @ S)   # [[ 0 -1] [ 1  1]]  전단 먼저, 그다음 회전

v = np.array([3, 2])
print(S @ (R @ v))   # [1 3]  돌아가는 길: 하나씩 적용
print((S @ R) @ v)   # [1 3]  지름길: 합성 행렬 한 번

실습 2 — 열 단위로 곱 구하기 (4.4를 코드로)

M1 = np.array([[1, -2], [1, 0]])
M2 = np.array([[0, 2], [1, 0]])

col1 = M2 @ M1[:, 0]   # î의 중간 도착점에 M2 적용 → [2 1]
col2 = M2 @ M1[:, 1]   # ĵ의 중간 도착점에 M2 적용 → [ 0 -2]
print(np.column_stack([col1, col2]))   # [[ 2  0] [ 1 -2]]
print(M2 @ M1)                         # 같은 결과

실습 3 — 결합법칙과 계산 속도

import time
rng = np.random.default_rng(0)
n = 2000
A = rng.standard_normal((n, n))
B = rng.standard_normal((n, n))
x = rng.standard_normal(n)

t = time.perf_counter(); y1 = A @ (B @ x); t1 = time.perf_counter() - t
t = time.perf_counter(); y2 = (A @ B) @ x; t2 = time.perf_counter() - t
print(np.allclose(y1, y2))           # True: 답은 같다
print(f'A(Bx): {t1:.4f}초, (AB)x: {t2:.4f}초')   # 속도는 크게 다르다

실습 4 — 그림으로 비교

3장 실습의 draw 함수를 그대로 가져와 draw(S @ R, '회전 → 전단'), draw(R @ S, '전단 → 회전')을 실행합니다. 두 격자가 다르게 기울어진 것을 눈으로 확인합니다.

4.10 용어 정리

한국어영어뜻
합성 변환composition한 변환을 한 뒤 다른 변환을 이어서 하는 것
행렬 곱matrix multiplication합성 변환의 행렬을 구하는 계산
교환법칙commutativityAB = BA. 행렬 곱에서는 대개 성립하지 않는다
결합법칙associativity(AB)C = A(BC). 행렬 곱에서 항상 성립한다
항등 행렬identity matrix아무것도 하지 않는 변환. IA = AI = A

4.11 연습문제

행×열 공식을 쓰지 말고, î와 ĵ를 따라가며 푸세요.

기본

  1. A = [[2, 0], [0, 1]](가로만 2배), B = 반시계 90° 회전이다. “B를 먼저, 그다음 A”의 행렬을 구하라. 식으로는 AB인가 BA인가?
  2. 1번의 순서를 바꾼 행렬을 구하고, 1번과 비교하라.
  3. [[1, 2], [3, 4]] × [[0, 1], [1, 0]]을 열 단위로 계산하라. 결과가 왜 그렇게 나오는지 설명하라.
  4. 반시계 90° 회전을 두 번 하면 어떤 행렬이 되는가? 어떤 변환인가?

응용

  1. 전단 S = [[1, 1], [0, 1]]을 두 번 하면 어떤 행렬이 되는가? 그림으로 의미를 설명하라.
  2. x축 기준 뒤집기 F = [[1, 0], [0, −1]]와 90° 회전 R에 대해 FR과 RF를 구하라. 같은가?
  3. 항등 행렬 I = [[1, 0], [0, 1]]에 대해 어떤 행렬 A든 IA = AI = A인 이유를 변환으로 설명하라.
  4. 큰 행렬 A, B와 벡터 x가 있을 때 ABx를 계산한다. A(Bx)와 (AB)x 중 어느 쪽이 빠르고, 답이 같은 이유는 무엇인가?
해설 펼치기
  1. B 먼저이므로 B가 오른쪽, 식은 AB입니다. î: B로 [0, 1] → A는 세로를 안 바꾸니 [0, 1]. ĵ: B로 [−1, 0] → A가 가로를 2배해 [−2, 0]. AB = [[0, −2], [1, 0]].
  2. BA. î: A로 [2, 0] → B로 [0, 2]. ĵ: A로 [0, 1] → B로 [−1, 0]. BA = [[0, −1], [2, 0]]. 1번과 다릅니다. 늘린 뒤 돌리는 것과 돌린 뒤 늘리는 것은 다릅니다.
  3. 오른쪽 행렬의 1열 [0, 1]: 0·[1, 3] + 1·[2, 4] = [2, 4]. 2열 [1, 0]: [1, 3]. 결과 [[2, 1], [4, 3]]. 오른쪽 행렬은 î와 ĵ의 자리를 맞바꾸는 변환입니다. 그래서 왼쪽 행렬의 열이 서로 바뀐 행렬이 나옵니다.
  4. î: [1, 0] → [0, 1] → [−1, 0]. ĵ: [0, 1] → [−1, 0] → [0, −1]. [[−1, 0], [0, −1]]. 180° 회전입니다. 모든 벡터가 반대 방향을 가리킵니다.
  5. î는 계속 [1, 0]. ĵ: [0, 1] → [1, 1] → 1·[1, 0] + 1·[1, 1] = [2, 1]. SS = [[1, 2], [0, 1]]. 한 번에 두 배로 밀어낸 전단입니다. 높이 y인 점이 오른쪽으로 2y만큼 밀립니다.
  6. FR(R 먼저): î → [0, 1] → [0, −1], ĵ → [−1, 0] → [−1, 0]. FR = [[0, −1], [−1, 0]]. RF(F 먼저): î → [1, 0] → [0, 1], ĵ → [0, −1] → [1, 0]. RF = [[0, 1], [1, 0]]. 다릅니다.
  7. I는 아무것도 하지 않는 변환입니다. “아무것도 안 하고 A” 또는 “A를 하고 아무것도 안 하기”는 그냥 A입니다.
  8. A(Bx)가 훨씬 빠릅니다. 행렬-벡터 곱 두 번(약 2n²)이 행렬-행렬 곱 한 번(약 n³)보다 가볍습니다. 답이 같은 이유는 결합법칙입니다. 양쪽 모두 “B를 하고 A를 한다”는 같은 동작을 같은 순서로 합니다.

다음 장 예고: 5장 “3차원 선형 변환”. 지금까지의 생각을 3차원 공간으로 넓힙니다.