행렬과 행렬 곱: 데이터를 한 번에 변환하기

행렬을 숫자의 표이자 데이터 묶음으로 이해하고, 행렬과 벡터의 곱, 행렬끼리의 곱, 전치와 단위 행렬을 익혀요. 신경망의 한 층이 행렬 곱 하나라는 사실과, GPU가 AI에 강한 이유까지 연결해요.

초급강의 2 / 6약 8분

지난 강의에서 데이터 하나를 벡터로 나타냈어요. 그런데 실제로는 데이터가 수천, 수백만 개예요. 벡터를 여러 개 쌓으면 행렬이 되고, 행렬 곱 한 번으로 수백만 번의 내적을 한꺼번에 처리할 수 있어요. 이번 강의는 AI 코드에서 가장 자주 일어나는 연산인 행렬 곱을 완전히 이해하는 게 목표예요.

행렬은 숫자의 표

행렬은 숫자를 직사각형으로 늘어놓은 표예요.

A=[123456]A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}

가로줄을 행, 세로줄을 열이라고 해요. AA는 행이 2개, 열이 3개라서 2×32 \times 3 행렬이에요. ii번째 행, jj번째 열의 원소를 aija_{ij}로 써요. 위에서 a23=6a_{23} = 6이에요.

데이터 행렬

머신러닝에서 데이터는 보통 이렇게 담아요.

  • 행 하나 = 데이터 하나 (집 한 채, 사람 한 명, 사진 한 장)
  • 열 하나 = 특징 하나 (면적, 방 수, 나이)
X=[843125922511243](집 3채, 특징 3개)X = \begin{bmatrix} 84 & 3 & 12 \\ 59 & 2 & 25 \\ 112 & 4 & 3 \\ \end{bmatrix} \quad \text{(집 3채, 특징 3개)}

엑셀 표를 떠올리면 정확해요. 데이터가 100만 개, 특징이 50개면 1,000,000×501{,}000{,}000 \times 50 행렬이에요.

이미지도 행렬

흑백 사진은 픽셀 밝기의 행렬이에요. 가로 28, 세로 28 픽셀이면 28×2828 \times 28 행렬이고, 이걸 한 줄로 펼치면 784차원 벡터예요. 컬러 사진은 빨강, 초록, 파랑 세 장의 행렬이 겹친 것이에요. 이렇게 행렬을 여러 장 쌓은 것을 텐서라고 부르고, 딥러닝 라이브러리는 모든 데이터를 텐서로 다뤄요.

행렬과 벡터의 곱

행렬 AA와 벡터 x\mathbf{x}의 곱은 A의 각 행과 x의 내적을 차례로 구한 거예요.

[123456][102]=[1⋅1+2⋅0+3⋅24⋅1+5⋅0+6⋅2]=[716]\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \begin{bmatrix} 1 \\ 0 \\ 2 \end{bmatrix} = \begin{bmatrix} 1 \cdot 1 + 2 \cdot 0 + 3 \cdot 2 \\ 4 \cdot 1 + 5 \cdot 0 + 6 \cdot 2 \end{bmatrix} = \begin{bmatrix} 7 \\ 16 \end{bmatrix}

2×32 \times 3 행렬에 3차원 벡터를 곱하면 2차원 벡터가 나와요. 행렬은 벡터를 받아 다른 벡터를 내놓는 함수예요. 3차원 입력을 2차원 출력으로 바꿨죠.

두 번째 시각: 열들의 선형 결합

같은 곱을 다르게 읽을 수도 있어요. 벡터의 성분을 가중치 삼아 행렬의 열들을 섞는 거예요.

1[14]+0[25]+2[36]=[716]1 \begin{bmatrix} 1 \\ 4 \end{bmatrix} + 0 \begin{bmatrix} 2 \\ 5 \end{bmatrix} + 2 \begin{bmatrix} 3 \\ 6 \end{bmatrix} = \begin{bmatrix} 7 \\ 16 \end{bmatrix}

이 시각은 다음 강의(선형 변환)의 핵심이에요. 두 가지 읽는 법을 모두 익혀 두세요.

신경망의 한 층

뉴런 하나는 입력과 가중치의 내적이었죠. 뉴런이 여러 개면 가중치 벡터를 행으로 쌓은 행렬 WW가 돼요.

h=Wx+b\mathbf{h} = W\mathbf{x} + \mathbf{b}

입력 784개(손글씨 이미지)를 받아 뉴런 128개로 보내는 층이라면 WW는 128×784128 \times 784 행렬이고, 행렬 곱 한 번이 128128개의 내적을 동시에 계산해요. 실제 신경망은 여기에 활성화 함수를 씌우는데, 그 이유는 다음 강의에서 볼게요.

행렬끼리의 곱

계산 규칙

C=ABC = AB의 원소 cijc_{ij}는 A의 i번째 행과 B의 j번째 열의 내적이에요.

[1234][5678]=[1⋅5+2⋅71⋅6+2⋅83⋅5+4⋅73⋅6+4⋅8]=[19224350]\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 1 \cdot 5 + 2 \cdot 7 & 1 \cdot 6 + 2 \cdot 8 \\ 3 \cdot 5 + 4 \cdot 7 & 3 \cdot 6 + 4 \cdot 8 \end{bmatrix} = \begin{bmatrix} 19 & 22 \\ 43 & 50 \end{bmatrix}

모양 규칙

곱하려면 앞 행렬의 열 수 = 뒤 행렬의 행 수여야 해요. 결과의 모양은 바깥 두 숫자예요.

(m×n‾)⋅(n‾×p)=(m×p)(m \times \underline{n}) \cdot (\underline{n} \times p) = (m \times p)

(2×3)(3×4)(2 \times 3)(3 \times 4)는 되고 결과는 2×42 \times 4예요. (2×3)(2×3)(2 \times 3)(2 \times 3)은 안 돼요. AI 코드에서 가장 흔한 오류가 이 모양 불일치(shape mismatch)예요. 오류가 나면 각 행렬의 모양을 출력해서 안쪽 숫자가 맞는지부터 확인하세요.

순서를 바꾸면 결과가 달라진다

숫자 곱셈은 3×5=5×33 \times 5 = 5 \times 3이지만 행렬 곱은 대부분 AB≠BAAB \neq BA예요. 모양부터 안 맞을 때도 많고, 모양이 맞아도 값이 달라요. 함수의 합성 순서를 바꾸면 결과가 달라지는 것과 같은 이유예요(행렬 곱은 함수의 합성이에요. 다음 강의에서 확인해요).

반면 이건 성립해요.

  • 결합 법칙: (AB)C=A(BC)(AB)C = A(BC)
  • 분배 법칙: A(B+C)=AB+ACA(B + C) = AB + AC

계산량

(m×n)(n×p)(m \times n)(n \times p) 곱셈에는 곱셈이 m×n×pm \times n \times p번 필요해요. 1000×10001000 \times 1000 두 개를 곱하면 10억 번이에요. 대형 언어 모델은 이런 곱셈을 토큰 하나를 만들 때마다 수없이 해요. AI의 비용과 속도가 결국 "행렬 곱을 얼마나 빨리, 얼마나 많이 하느냐"로 정해지는 이유예요.

GPU가 AI에 강한 이유

행렬 곱의 각 원소 cijc_{ij}는 서로 독립적으로 계산할 수 있어요. 원소 100만 개를 100만 명이 동시에 계산해도 돼요.

  • CPU: 아주 똑똑한 일꾼 몇 명(코어 수십 개). 복잡하고 순서가 중요한 일에 강해요.
  • GPU: 단순한 계산을 하는 일꾼 수천 명. 똑같은 계산을 대량으로 동시에 하는 일에 강해요.

원래 게임 화면의 픽셀 수백만 개를 동시에 칠하려고 만든 GPU가, 구조가 똑같은 행렬 곱에 딱 맞았던 거예요. 딥러닝 붐은 이 우연한 궁합에서 시작됐어요.

특별한 행렬과 연산

전치

행과 열을 바꾼 것이 전치(ATA^T)예요.

[123456]T=[142536]\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix}

벡터의 내적을 행렬 곱으로 쓸 때 자주 나와요. 세로 벡터 u\mathbf{u}, v\mathbf{v}의 내적은 uTv\mathbf{u}^T \mathbf{v}예요. 성질 하나만 기억하세요. 곱의 전치는 순서가 뒤집혀요: (AB)T=BTAT(AB)^T = B^T A^T.

단위 행렬

대각선이 1이고 나머지가 0인 정사각 행렬 II는 곱해도 아무것도 바뀌지 않아요. 숫자의 1과 같은 역할이에요.

I=[1001],AI=IA=AI = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}, \qquad AI = IA = A

대각 행렬

대각선에만 값이 있는 행렬은 각 축을 따로따로 늘이거나 줄여요.

[2003][xy]=[2x3y]\begin{bmatrix} 2 & 0 \\ 0 & 3 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 2x \\ 3y \end{bmatrix}

넘파이로 행렬 다루기

import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print(A @ B)         # 행렬 곱 [[19 22] [43 50]]
print(B @ A)         # [[23 34] [31 46]]  순서가 바뀌면 다름
print(A * B)         # 자리끼리 곱 [[5 12] [21 32]]  행렬 곱이 아님!
print(A.T)           # 전치
print(np.eye(2))     # 단위 행렬
print(A.shape)       # (2, 2)

데이터 한꺼번에 처리하기

집 3채의 특징 행렬 XX와 특징별 가격 가중치 w\mathbf{w}가 있으면, 세 집의 예상 가격을 한 번에 구할 수 있어요.

X = np.array([
    [84, 3, 12],
    [59, 2, 25],
    [112, 4, 3],
])
w = np.array([800, 1500, -50])   # ㎡당 800만, 방당 1500만, 연식 1년당 -50만 (예시 값)
prices = X @ w                    # 집마다 가중합 (만 원)
print(prices)                     # [71100 48950 95450]

데이터가 100만 개여도 코드는 똑같이 한 줄이에요. 이것이 머신러닝의 선형 회귀 모델 그 자체예요. 학습은 데이터를 보며 좋은 w\mathbf{w}를 찾는 과정이고, 머신러닝 트랙에서 직접 해 봐요.

브로드캐스팅

넘파이는 모양이 다른 배열끼리도 규칙에 맞으면 알아서 늘려서 계산해요.

X_centered = X - X.mean(axis=0)   # 각 열(특징)의 평균을 빼기

X.mean(axis=0)은 길이 3인 벡터인데, 3×33 \times 3 행렬의 모든 행에서 빼 줘요. 편리하지만, 의도와 다른 모양으로 조용히 계산될 수도 있으니 결과의 shape를 확인하는 습관을 들이세요.

실습

  1. A=[2013]A = \begin{bmatrix} 2 & 0 \\ 1 & 3 \end{bmatrix}, x=(1,2)\mathbf{x} = (1, 2)일 때 AxA\mathbf{x}를 손으로 계산하고 넘파이로 확인하세요.
  2. 모양이 (4×3)(4 \times 3), (3×5)(3 \times 5), (5×2)(5 \times 2)인 행렬 AA, BB, CC의 곱 ABCABC의 모양은? 곱셈 횟수가 적은 쪽은 (AB)C(AB)C와 A(BC)A(BC) 중 어느 쪽일까요?
  3. 넘파이로 500×500500 \times 500 행렬 두 개를 곱하는 시간과, 같은 계산을 파이썬 3중 반복문으로 하는 시간을 비교해 보세요(반복문은 100×100100 \times 100으로 줄여도 충분해요).

자주 하는 실수

  • *와 @ 혼동. 넘파이에서 *는 자리끼리 곱이에요. 행렬 곱은 @예요.
  • 모양 확인 안 하기. 오류가 없어도 브로드캐스팅 때문에 엉뚱한 모양으로 계산될 수 있어요.
  • 곱셈 순서 바꾸기. ABAB와 BABA는 다르다는 걸 늘 기억하세요.
  • 행과 열의 뜻 헷갈리기. 데이터 행렬에서 행은 데이터, 열은 특징이라는 약속을 지키세요.

점검 문제

  1. (3×2)(3 \times 2) 행렬과 (2×4)(2 \times 4) 행렬의 곱의 모양은?
  2. [1101][32]\begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} \begin{bmatrix} 3 \\ 2 \end{bmatrix}는?
  3. 입력 300차원, 출력 100차원인 신경망 층의 가중치 행렬 모양은?
  4. 행렬 곱의 각 원소는 무엇과 무엇의 내적인가요?

정답

  1. 3×43 \times 4
  2. (5,2)(5, 2). 첫 행 (1,1)(1, 1)과의 내적은 5, 둘째 행 (0,1)(0, 1)과의 내적은 2예요.
  3. WxW\mathbf{x} 꼴이면 100×300100 \times 300이에요(출력 수 × 입력 수).
  4. 앞 행렬의 행과 뒤 행렬의 열의 내적이에요.

다음 강의

다음 강의에서는 행렬을 공간을 움직이는 함수로 봐요. 회전, 확대, 기울이기가 모두 행렬 하나로 표현되고, 행렬 곱이 왜 함수의 합성인지가 그림처럼 이해될 거예요.