수학

MATH / 중급

논문 수식 표기 규약: 스칼라·벡터·행렬·텐서를 구별해서 읽기

굵은 글씨와 대문자, 위·아래 첨자, 슬라이스와 ⊙·⊗·‖·‖가 각각 무엇을 뜻하는지 정리하고, 논문의 기호를 코드의 텐서 축에 하나씩 맞춰 봅니다.

PALDYN Team19 MIN READ

논문에 적힌 식을 그대로 코드로 옮겼는데 이런 게 뜹니다.

RuntimeError: mat1 and mat2 shapes cannot be multiplied (512x768 and 768x512)

식에는 분명히 y=Wxy = Wx라고 적혀 있었고, 그대로 W @ x라고 썼습니다. 그런데 안 됩니다. 순서를 바꿔 x @ W로 하면 되기도 하고 여전히 안 되기도 합니다. 이쯤 되면 셰이프를 하나씩 출력해 보며 맞을 때까지 전치를 붙였다 뗐다 하게 되는데, 그건 식을 읽은 게 아니라 맞춘 것입니다.

지난 글에서 어텐션 한 줄을 다섯 자리로 쪼개면서, 첫째 자리에 걸린 질문이 "QQ가 벡터인가 행렬인가"였습니다. 이 글이 그 질문을 답합니다. 논문은 어떤 기호가 몇 개의 축을 가진 대상인지를 글자 모양으로 알립니다. 그 규약을 알면 셰이프 에러의 절반은 코드를 돌리기 전에 사라집니다.

글꼴이 곧 자료형이다

대부분의 머신러닝 논문은 다음 네 층을 글자 모양으로 구별합니다.

스칼라·벡터·행렬·텐서의 표기 사다리

표기 대상 축 개수 코드에서의 셰이프 AI에서의 예
dd, nn, α\alpha 스칼라 0 () 학습률, 차원 수
x\mathbf{x}, q\mathbf{q} 벡터 1 (768,) 토큰 임베딩 하나
X\mathbf{X}, W\mathbf{W} 행렬 2 (512, 768) 문장 하나의 은닉 상태
X\mathcal{X}, T\mathcal{T} 텐서 3 이상 (8, 512, 768) 배치 전체의 은닉 상태

몇 가지 실전 요령이 있습니다.

굵게 쓰지 않는 논문도 많습니다. 특히 Attention Is All You Need처럼 굵은 글씨를 안 쓰는 논문에서는 QQ가 대문자라는 것 하나로 행렬임을 알아야 합니다. 대문자는 거의 항상 행렬 이상이라고 보면 맞습니다. 소문자면 스칼라 아니면 벡터인데, 그 둘은 문맥과 첨자로 갈립니다 — qiq_i처럼 첨자가 붙어 있으면 보통 벡터들의 모임에서 하나를 꺼낸 것이라 벡터입니다.

필기체 X\mathcal{X}는 텐서 말고 집합에도 씁니다. D\mathcal{D}가 데이터셋, V\mathcal{V}가 어휘 집합, N(0,1)\mathcal{N}(0, 1)이 정규분포를 뜻하는 식입니다. 무엇인지는 그 기호가 처음 정의된 문장에서 확인해야 하고, 그 문장이 바로 Notation 절에 있습니다.

희랍 문자는 대개 학습 대상이 아닌 값입니다. α\alpha(학습률), β\beta(모멘텀 계수), ϵ\epsilon(수치 안정용 작은 수), λ\lambda(정규화 세기), τ\tau(온도). 반면 θ\thetaϕ\phi는 예외적으로 "모델 파라미터 전부"를 한 글자로 묶은 것이고, 그래서 pθp_\theta는 "파라미터가 θ\theta인 모델이 주는 확률"로 읽습니다.

열벡터 관례 — 셰이프 에러의 진짜 원인

앞의 에러로 돌아갑니다. y=Wxy = Wx가 코드에서 W @ x로 안 되는 이유는, 논문의 xx가 열벡터이기 때문입니다.

수학에서 아무 말 없이 xRdx \in \mathbb{R}^d라고 쓰면 그것은 d×1d \times 1짜리 세로 벡터입니다. 그래서 WRd×dW \in \mathbb{R}^{d' \times d}를 왼쪽에 곱하면 (d×d)(d×1)=d×1(d' \times d)(d \times 1) = d' \times 1로 모양이 맞습니다.

Wd×d  xd×1=yd×1\underbrace{W}_{d' \times d} \; \underbrace{x}_{d \times 1} = \underbrace{y}_{d' \times 1}

그런데 코드에서 텐서 하나는 벡터 하나가 아니라 배치입니다. 토큰 512개를 한 번에 처리하니 x.shape == (512, 768)이고, 토큰 하나하나가 으로 누워 있습니다. 행으로 누운 벡터에 변환을 걸려면 오른쪽에서 곱해야 합니다.

Xn×d  Wd×d=Yn×d\underbrace{X}_{n \times d} \; \underbrace{W^\top}_{d \times d'} = \underbrace{Y}_{n \times d'}

열벡터 관례와 행 우선 관례

논문의 WxWx는 코드에서 x @ W.T입니다. 그리고 PyTorch의 nn.Linear는 이 전치를 안에서 대신 해 줍니다 — nn.Linear(d, d')weight 셰이프가 (d', d)인 것이 그 증거입니다. 논문의 WW와 같은 모양으로 저장해 두고, forward에서 x @ self.weight.T를 합니다.

여기서 한 가지가 따라 나옵니다. 어텐션 식이 QKQK^\top인 것도 이 관례 때문입니다. QQKK가 둘 다 토큰을 행에 쌓은 n×dkn \times d_k이므로, 모든 쌍의 내적을 얻으려면 오른쪽 것을 전치해야 합니다. 만약 논문이 토큰을 열에 쌓는 관례를 택했다면 같은 계산이 QKQ^\top K로 적혔을 겁니다. 식이 달라 보여도 계산은 같습니다. 논문을 읽을 때 가장 먼저 확인할 것은 "이 논문은 토큰을 행에 쌓는가 열에 쌓는가"입니다.

위 첨자와 아래 첨자 — 인덱스인가 라벨인가

첨자는 두 가지 일을 합니다. 하나는 인덱스(몇 번째인가), 다른 하나는 라벨(어느 것인가). 둘을 구별하는 규칙은 대체로 이렇습니다.

표기 읽는 법 코드 대응
xix_i ii번째 원소 또는 ii번째 벡터 x[i]
XijX_{ij} iijj열 원소 (스칼라) X[i, j]
h(l)h^{(l)} ll번째 층의 은닉 상태 층 인덱스 — 거듭제곱이 아니다
x(i)x^{(i)} ii번째 데이터 샘플 batch[i]
WQW_Q, WKW_K 질의용·키용 가중치 (라벨) 서로 다른 파라미터
x2x^2 제곱 x ** 2
XX^\top 전치 X.T
A1A^{-1} 역행렬 np.linalg.inv(A)

가장 자주 헷갈리는 자리가 괄호 친 위 첨자입니다. h(l)h^{(l)}(l)(l)은 거듭제곱이 아니라 층 번호입니다. 괄호가 있으면 인덱스, 없으면 거듭제곱 — 이것이 관례고, 그래서 논문에서 h(l+1)=f(h(l))h^{(l+1)} = f(h^{(l)}) 같은 식이 자연스럽게 읽힙니다. 반면 아래 첨자에 붙은 알파벳 라벨(WQW_QQQ)은 세지 않습니다. 그건 이름의 일부입니다.

dkd_k, dvd_v, dmodeld_{\text{model}}도 같은 이치입니다. ddkk를 곱한 것이 아니라, "키 벡터의 차원"이라는 이름 하나입니다.

원소와 슬라이스 — 콜론의 자리

한 축 전체를 가져오는 표기는 콜론입니다. 논문에서도 코드에서도 같습니다.

표기 셰이프 코드
XijX_{ij} 원소 하나 스칼라 X[i, j]
Xi,:X_{i,:} ii번째 행 전체 (d,)(d,) X[i, :]
X:,jX_{:,j} jj번째 열 전체 (n,)(n,) X[:, j]
Hb,t,:\mathcal{H}_{b,t,:} 배치 bbtt번 토큰 (d,)(d,) h[b, t, :]

콜론을 생략하는 논문도 많습니다. XiX_i라고만 써 놓고 그것이 ii번째 을 뜻하는 경우가 흔한데, 앞에서 본 행 우선 관례 때문입니다. 토큰을 행에 쌓았으니 "ii번째 것"은 자연히 ii번째 행입니다.

여기서 읽기의 핵심은 셰이프가 줄어드는가 유지되는가입니다. 숫자 인덱스는 그 축을 없애고, 콜론은 그 축을 남깁니다. Hb,t,:\mathcal{H}_{b,t,:}는 세 축 중 둘이 사라져 벡터 하나가 되고, Hb,:,:\mathcal{H}_{b,:,:}는 한 축만 사라져 행렬이 됩니다. 이 감각이 있으면 식을 보고 결과의 셰이프를 미리 말할 수 있습니다.

연산 기호 다섯 개

논문에서 자주 나오면서 코드 연산자와 헷갈리는 기호들입니다.

\odot — 아다마르 곱(원소별 곱). 같은 셰이프 두 개를 원소끼리 곱합니다. 행렬곱이 아닙니다.

(AB)ij=AijBij(A \odot B)_{ij} = A_{ij} B_{ij}

코드로는 A * B입니다. LSTM의 게이트, 드롭아웃 마스크, LayerNorm의 스케일 파라미터 γx^\gamma \odot \hat{x}가 전부 이 기호입니다. NumPy에서 *가 행렬곱이 아니라 원소별 곱인 이유가 여기 있습니다 — 행렬곱은 @로 따로 뺐습니다.

\otimes — 외적 또는 크로네커 곱. 벡터 두 개로 행렬을 만듭니다. aRma \in \mathbb{R}^m, bRnb \in \mathbb{R}^n일 때 aba \otimes bm×nm \times n 행렬이고 (ab)ij=aibj(a \otimes b)_{ij} = a_i b_j입니다. 내적이 축을 없애 스칼라를 만든다면 외적은 축을 더해 행렬을 만듭니다. 코드로는 np.outer(a, b) 또는 a[:, None] * b[None, :]입니다.

\lVert \cdot \rVert — 노름. 벡터의 크기입니다. 아래 첨자로 어떤 노름인지 밝힙니다: x2\lVert x \rVert_2는 유클리드 길이, x1\lVert x \rVert_1은 절댓값의 합. 아래 첨자가 없으면 대개 x2\lVert x \rVert_2입니다. 그래디언트 클리핑, 가중치 감쇠, 코사인 유사도의 분모가 전부 이 기호이고, 다음 글이 이것만 다룹니다.

,\langle \cdot, \cdot \rangle — 내적. a,b\langle a, b \rangleaba \cdot baba^\top b와 같은 것을 다르게 쓴 것입니다. 세 표기가 논문마다 섞여 나오는데 전부 같은 계산입니다.

E\mathbb{E} — 기댓값. 아래 첨자에 무엇에 대한 평균인지를 적습니다. Exp[f(x)]\mathbb{E}_{x \sim p}[f(x)]는 "pp에서 뽑은 xx에 대해 f(x)f(x)의 평균". 코드에서는 거의 항상 배치 평균, 즉 f(x).mean()으로 구현됩니다. 이 기호가 4단원부터 계속 나옵니다.

f: ℝⁿ → ℝᵐ 을 읽기

함수를 소개하는 한 줄이 논문 곳곳에 있습니다.

f:RnRmf: \mathbb{R}^{n} \rightarrow \mathbb{R}^{m}

"ffnn차원 실수 벡터를 받아 mm차원 실수 벡터를 내놓는다"로 읽습니다. 화살표 왼쪽이 정의역(무엇을 받는가), 오른쪽이 공역(어디에 값이 놓이는가)입니다. 코드로는 함수의 입력 셰이프와 출력 셰이프를 한 줄로 선언한 것이고, 사실상 타입 힌트입니다.

여기서 자주 놓치는 구별이 공역과 상입니다. 공역은 "값이 놓일 수 있는 공간"이고, 상(image)은 "실제로 나오는 값들의 모임"입니다. 둘은 다를 수 있고, 그 차이가 의미를 가지는 자리가 있습니다.

softmax가 그렇습니다.

softmax:RnRn\mathrm{softmax}: \mathbb{R}^{n} \rightarrow \mathbb{R}^{n}

공역은 Rn\mathbb{R}^n 전체지만, 실제로 나오는 값은 "모든 성분이 양수이고 합이 1인 벡터"뿐입니다. 그 부분집합을 확률 단체(simplex)라 부르고 Δn1\Delta^{n-1}로 씁니다. 그래서 더 정확한 표기는 이렇습니다.

softmax:RnΔn1\mathrm{softmax}: \mathbb{R}^{n} \rightarrow \Delta^{n-1}

이 구별이 실무에서 의미를 갖는 이유는, softmax의 출력이 절대 도달하지 못하는 값이 있기 때문입니다. 성분이 정확히 0이나 정확히 1인 벡터는 상에 없습니다 — eze^{z}는 어떤 실수 zz에 대해서도 0이 되지 않으니까요. 지난 글에서 본 마스킹이 -\infty를 넣는 이유가 여기 있습니다. 유한한 값으로는 가중치를 정확히 0으로 만들 수 없고, 극한으로 밀어야만 0이 됩니다. 그리고 실제 구현이 -\infty 대신 -1e9를 쓰는 순간 그 0은 "아주 작은 양수"가 됩니다.

손으로 따라가기 — LayerNorm 식을 셰이프까지 읽기

지금까지의 규약으로 실제 식 하나를 끝까지 읽어 봅니다. LayerNorm입니다.

LN(x)=γxμσ2+ϵ+β\mathrm{LN}(x) = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta

μ=1di=1dxi,σ2=1di=1d(xiμ)2\mu = \frac{1}{d}\sum_{i=1}^{d} x_i, \qquad \sigma^2 = \frac{1}{d}\sum_{i=1}^{d} (x_i - \mu)^2

기호를 하나씩 분류합니다.

기호 무엇인가 셰이프 근거
xx 벡터 (토큰 하나의 은닉) (d,) 소문자에 첨자로 원소를 꺼냄
xix_i 스칼라 () 숫자 인덱스가 축을 없앰
μ\mu, σ2\sigma^2 스칼라 () dd개를 합쳐 하나로
ϵ\epsilon 스칼라 상수 () 희랍 문자, 학습 대상 아님
γ\gamma, β\beta 벡터 (학습 파라미터) (d,) \odot의 상대이므로 xx와 같은 셰이프
\odot 원소별 곱 행렬곱이면 셰이프가 안 맞음

γ\gamma가 벡터라는 것은 식 어디에도 안 적혀 있습니다. \odot가 원소별 곱이고 상대가 (d,)이므로 γ\gamma(d,)여야 한다는 것으로 알아냅니다. 이렇게 연산자가 셰이프를 강제하는 관계를 따라가는 것이 수식 읽기의 실제 작업입니다.

이제 배치로 확장합니다. 코드에서 x.shape == (B, T, d)라면 어느 축으로 평균을 낼까요? 식의 i=1d\sum_{i=1}^{d}가 답입니다. dd에 대해서만 더하므로 마지막 축입니다.

import numpy as np

B, T, d = 2, 4, 8
rng = np.random.default_rng(0)
x = rng.normal(size=(B, T, d))
gamma = rng.normal(size=(d,))
beta = rng.normal(size=(d,))
eps = 1e-5

mu = x.mean(axis=-1, keepdims=True)        # (B, T, 1)
var = x.var(axis=-1, keepdims=True)        # (B, T, 1)
out = gamma * (x - mu) / np.sqrt(var + eps) + beta

print(out.shape)                            # (2, 4, 8)
print(np.allclose(((x - mu) / np.sqrt(var + eps)).mean(-1), 0, atol=1e-12))
print(np.allclose(((x - mu) / np.sqrt(var + eps)).std(-1), 1, atol=1e-4))

keepdims=True가 필요한 이유도 표기에서 나옵니다. μ\mu는 스칼라지만 xμx - \mu에서는 dd개 성분 전부에서 빼야 합니다. 수식은 이 반복을 안 적고 넘어가지만 — 스칼라를 벡터에서 빼는 것을 당연하게 봅니다 — 코드에서는 축을 남겨 두어야 브로드캐스팅이 그 반복을 대신합니다. 논문 식이 생략한 것을 코드가 명시해야 하는 대표적인 자리입니다.

다시 그 에러로

처음의 에러를 다시 봅니다.

RuntimeError: mat1 and mat2 shapes cannot be multiplied (512x768 and 768x512)

이제 이 메시지는 버그가 아니라 관례 충돌로 읽힙니다. 논문의 WWd×dd' \times d로 정의됐고 코드의 x는 토큰을 행에 쌓은 n×dn \times d입니다. WxWx를 그대로 옮기면 축이 안 맞고, 옳은 번역은 x @ W.T입니다. 셰이프를 출력해 가며 전치를 붙였다 뗐다 할 필요가 없습니다 — 식을 보고 미리 알 수 있습니다.

이 글의 규약으로 아직 못 읽는 것이 하나 남아 있습니다. i=1d\sum_{i=1}^{d} 같은 합 기호에서, 어떤 첨자가 결과에 남고 어떤 첨자가 합쳐져 사라지는지입니다. LayerNorm에서는 ii가 사라지고 남는 축이 없어 스칼라가 됐지만, QKQK^\top처럼 첨자가 셋 이상 얽히면 눈으로 따라가기 어렵습니다. 다음 글이 그 규칙을 정하고, 그것을 einsum 한 줄로 옮기는 방법까지 갑니다.


지난 글: 어텐션 식 한 줄에 들어 있는 수학 전부: 이 커리큘럼의 지도

다음 글: Σ와 첨자, 그리고 einsum: 수식을 그대로 코드로 옮기기


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

KEEP READING

이어 읽기

수학2026.08.05

노름과 거리: L1·L2·L∞와 단위구의 모양

노름의 세 공리에서 출발해 L1·L2·L∞의 단위구를 그려 비교하고, 그래디언트 클리핑과 가중치 감쇠와 L1 희소성이 각각 어떤 성질에서 나오는지 연결합니다.

중급22 MIN
PALDYN / AI LAB

AI를 이해하고 배우는 데 필요한 개념, 수학, 논문과 실험을 연결해 기록합니다.

OfficialTech Blog© 2026 PALDYN