행렬곱이 입력 벡터를 여러 선형변환으로 밀어냅니다 | DAKER 커뮤니티

딥러닝 모델이 숫자를 다루는 가장 기본 단위는 벡터와 행렬입니다. 픽셀, 토큰 임베딩, 센서 값, 표의 한 행은 모두 벡터로 표현됩니다. 가중치 한 층은 행렬입니다. 행렬과 벡터를 곱하면 입력이 다른 좌표로 옮겨집니다. 이 글은 그 한 장면만 풀어, 빌더가 레이어를 볼 때 머릿속에 그림을 그리도록 돕습니다.

행렬곱과 선형변환 썸네일

선형대수를 시험용 공식 모음으로만 두면, nn.Linear나 어텐션의 Q·K·V 투영이 추상적으로만 남습니다. 반대로 행렬곱을 여러 선형변환을 한 번에 적용하는 연산으로 읽으면, 모델 코드의 대부분 줄이 같은 동작의 반복임을 바로 알 수 있습니다. 해커톤과 대회에서 반복되는 패턴도 같습니다. 입력을 숫자 배열로 만들고, 선형층과 비선형층을 교차해 쌓습니다. 25만 빌더·339+ 대회 규모의 현장에서 우승 솔루션을 뜯어보아도, 뼈대는 결국 벡터를 행렬로 옮기는 연산의 조합입니다. 새로운 아키텍처 이름이 나와도, 읽기의 첫 질문은 동일합니다. 여기서 어떤 벡터가 어떤 행렬을 만나 어디로 가는가.

벡터는 방향과 크기를 가진 숫자 묶음입니다

벡터는 순서가 있는 숫자열입니다. 이미지 패치의 RGB 통계, 문장 임베딩의 수백 차원, 표에서 키·몸무게·나이처럼 특성이 나란히 놓인 값이 벡터입니다. 기하학적으로는 화살표로 그릴 수 있습니다. 길이는 크기이고, 가리키는 쪽은 방향입니다. 모델은 이 화살표를 받아 다음 화살표로 바꿉니다. 차원이 3이면 공간을 상상하기 쉽고, 768이면 상상은 어렵지만 연산 규칙은 동일합니다. 고차원이라고 해서 덧셈·스칼라곱·내적의 의미가 바뀌지는 않습니다.

내적은 두 벡터가 얼마나 같은 방향을 보는지의 점수입니다. 같은 방향이면 값이 커지고, 직교에 가까우면 0에 가까워지며, 반대 방향이면 음수가 됩니다. 코사인 유사도는 그 점수를 각 벡터 길이로 나눈 정규화 버전입니다. 검색·추천·RAG에서 임베딩을 비교할 때 쓰는 바로 그 연산입니다. 즉, 비슷한 의미를 숫자로 재는 도구가 내적 가족입니다. 어텐션의 점수 계산도 본질적으로는 질의 벡터와 키 벡터의 내적입니다.

노름은 벡터의 길이입니다. L2 노름이 가장 흔합니다. 기울기 클리핑에서 기울기 벡터의 노름을 자르는 이유도, 한 스텝의 이동량이 폭주하지 않게 막기 위함입니다. 배치 정규화·레이어 정규화는 활성화 벡터의 통계를 맞춰 학습을 안정화하는 기법으로, 역시 벡터를 다루는 연산입니다. 이름을 외우기 전에 이 줄이 화살표의 길이와 방향을 어떻게 만지는가만 질문하면 충분합니다.

행렬은 벡터를 다른 벡터로 보내는 규칙입니다

행렬 W가 있을 때 출력 z = Wx 는 입력 x의 각 성분을 가중합해 새 성분들을 만든다는 뜻입니다. 행마다 하나의 선형 방정식이 있습니다. 기하적으로는 스케일(늘리기·줄이기), 회전, 기울임(전단), 투영(낮은 차원으로 찌그러뜨리기) 같은 선형변환을 행렬 하나로 표현합니다. 편향 b를 더하면 z = Wx + b 가 되어, 원점을 지나지 않는 평행이동까지 포함합니다. 완전연결 선형층이 실제로 하는 일입니다. 코드를 읽을 때 Linear는 마법이 아니라 이 식의 이름표입니다.

열을 기준으로 읽으면 다른 직관이 생깁니다. Wx는 W의 열벡터들을 x의 성분으로 가중합한 결과입니다. 입력의 각 특성이 어느 방향으로 얼마나 기여하는가를 열이 담고 있습니다. 학습은 그 열들을 유용한 방향으로 돌리는 과정으로 읽을 수 있습니다. 행 기준으로 읽으면 출력의 각 뉴런이 입력을 어떻게 가중하는가입니다. 같은 행렬을 두 시선으로 보는 습관이 shape 디버깅에 도움이 됩니다.

중요합니다. 선형변환만 여러 번 합성하면 결과는 다시 하나의 큰 선형변환입니다. W₂(W₁x) = (W₂W₁)x 이기 때문입니다. 층을 아무리 깊게 쌓아도 중간에 비선형성이 없으면 표현력은 한 층과 같아집니다. 그래서 활성화 함수가 필요합니다. 그 이야기는 같은 시리즈의 활성화 함수 글에서 이어집니다. 지금은 한 행렬이 곧 한 선형 맵이라는 문장만 고정하면 됩니다.

행렬곱이 여러 선형변환의 합성임을 보여주는 다이어그램

행렬곱은 변환을 순서대로 이어 붙입니다

행렬곱 AB는 먼저 B를 적용하고 다음에 A를 적용한다는 합성입니다. 함수 합성 (f∘g)(x) = f(g(x)) 와 같은 순서 감각입니다. 딥러닝에서 층을 쌓는다는 말은, 곧 변환을 순서대로 쌓는다는 말입니다. 다만 각 층 사이에 비선형 활성화를 끼워 넣어, 전체가 하나의 행렬로 붕괴하지 않게 만듭니다. 순서를 바꾸면 결과가 달라질 수 있으므로, 레이어 순서는 설계의 일부입니다.

배치 차원까지 넣으면 입력이 행렬 X(예: 배치×특성)가 됩니다. 그러면 Z = XW 형태로 한 번에 여러 샘플을 처리합니다. GPU가 빠른 이유가 여기에 가깝습니다. 같은 규칙의 곱셈·덧셈을 거대한 배열에 병렬로 적용하기 때문입니다. 빌더가 배치 크기를 키우면 처리량이 오르는 현상도, 결국 행렬 연산 효율과 맞닿아 있습니다. 혼합 정밀도 학습이 속도를 내는 이유도, 행렬곱 커널을 더 빨리 돌리기 위한 하드웨어 경로를 쓰기 때문입니다.

전치(Wᵀ)는 행과 열을 바꾼 행렬입니다. 역전파에서 앞으로 곱했던 행렬을 반대로 흘려 보낼 때 전치가 자주 등장합니다. 지금은 이름만 알아 두십시오. 역전파 글에서 연쇄법칙과 함께 다시 만납니다. 대칭 행렬·직교 행렬 같은 특수 형태는, 변환이 길이를 보존하는지·축을 정렬하는지 같은 성질을 담는 용어입니다. 필요해질 때 찾아도 늦지 않습니다.

모델 코드에서 만나는 같은 그림

완전연결층은 Wx+b 그 자체입니다. 합성곱은 작은 필터를 이미지 국소 영역에 반복해 내적하는 형태로 이해할 수 있습니다. 국소 연결·가중치 공유라는 제약이 붙은 선형연산입니다. 어텐션의 Q = XW_Q, K = XW_K, V = XW_V 역시 입력 시퀀스를 세 가지 선형변환으로 보낸 뒤, 유사도(내적)로 가중치를 만들고 V를 가중합합니다. 이름이 달라도 뼈대는 벡터·행렬·내적입니다.

임베딩 테이블도 행렬입니다. 토큰 id가 행을 고르면, 그 행 벡터가 토큰의 초기 표현이 됩니다. one-hot 벡터에 임베딩 행렬을 곱한 것과 같습니다. 언어모델의 출력 로짓 역시 마지막 은닉 벡터에 큰 행렬을 곱은 결과에 가깝습니다. 어휘 크기만큼의 점수를 한 번에 만드는 선형층입니다. 분류 헤드의 Dense도 같은 가족입니다.

차원 숫자를 읽을 때도 같은 습관이 도움이 됩니다. (배치, 토큰, d_model) 텐서가 Linear(d_model, d_ff)를 지나면 마지막 축만 d_ff로 바뀝니다. 마지막 축에 행렬을 곱한다고 읽으면 shape 오류를 훨씬 빨리 찾습니다. 행렬이 무엇을 바꾸는지 알아야 shape를 설계할 수 있습니다. einsum 표기나 einops 정리는 그 축 계약을 명시적으로 적는 도구입니다. 에러 메시지보다 먼저 축 이름을 종이에 적어 보십시오.

작은 숫자로 한 번만 손으로 계산합니다

연습 한 가지를 제안합니다. W = [[2, 0], [0, 0.5]], x = [1, 1] 이면 Wx = [2, 0.5] 입니다. x축 방향은 늘리고 y축 방향은 줄인 결과입니다. 이번에는 W = [[0, −1], [1, 0]] 을 같은 x에 곱해 회전 감각을 느껴 보십시오. 학습이 W의 숫자를 바꿔 유용한 늘리기·줄이기·기울이기를 찾는 과정임을, 이 작은 예에서 먼저 고정합니다. 노트북에서 난수로 만든 3×3 행렬과 길이 3 벡터를 곱해 출력 변화를 관찰하는 것도 좋은 워밍업입니다.

정규화·고유값·특이값 감각은 나중에 얹으면 됩니다. 고유값·특이값이 크면 그 방향으로 신호가 증폭됩니다. PCA·SVD·스펙트럼 정규화는 모두 행렬이 공간을 어떻게 늘리고 줄이는가의 심화입니다. 교재 한 권을 통독하기 전에, 먼저 Wx가 입력 화살표를 어떻게 옮기는지 고정하는 편이 빌더에게 효율적입니다. 29억+ 규모의 데이터·연산이 오가는 플랫폼에서도, 디버깅의 첫 질문은 여전히 작은 행렬식에서 시작합니다.

오늘 빌더가 가져갈 체크리스트

이 글이 아닌 것

이 글은 고유값 분해 증명이나 모든 행렬 분해 기법을 다루는 교재가 아닙니다. 모델 코드에 바로 붙는 벡터·행렬·행렬곱 직관만 다룹니다.

이 글은 특정 프레임워크 API 레퍼런스가 아닙니다. PyTorch·JAX·TensorFlow 모두에서 같은 수학이 동작한다는 점만 공유합니다.

이 글은 최적화 이론이나 일반화 증명으로 넘어가는 글이 아닙니다. 그 전에 필요한 좌표계만 깔아 둡니다.

한 줄로 줄이면 이렇습니다. 행렬곱이 입력 벡터를 여러 선형변환으로 밀어냅니다. 빌더는 그 문장으로 레이어를 읽기 시작하면 됩니다.