[LLM.zip | 압축 해제 ] 2. Text에서 Vector까지의 여정 - Tokenization & Embedding

ju·6일 전

LLM.zip | 압축 해제

목록 보기
4/9
post-thumbnail

2. Text는 어떻게 Vector가 될까? — Tokenization & Embedding

지난 글에서는 LLM이 언어를 계산하기 위해 사용하는 Vector, Matrix, Tensor와 그 위에서 이루어지는 Dot Product, Cosine Similarity, Softmax를 살펴봤다.

그런데 한 가지 중요한 질문이 남아 있다.

우리가 LLM에게 입력하는 것은 Vector가 아니다.

"오늘 저녁 메뉴를 추천해줘."

우리가 입력하는 것은 Text다.

반면 Transformer가 실제로 계산하는 것은 숫자로 이루어진 Tensor다.

그렇다면 이 사이에서는 무슨 일이 일어날까?

Raw Text
   ↓
Tokenizer
   ↓
Tokens
   ↓
Token IDs
   ↓
Embedding
   ↓
Token Vectors
   ↓
Position Information
   ↓
Input Representations
   ↓
Transformer

이번 글에서는 이 변환 과정을 따라가 본다.

단순히

Tokenization = 문장을 나누는 것
Embedding = Vector로 바꾸는 것

정도로 끝내는 것이 아니라,

왜 Text를 Token으로 나눠야 하는지, Token은 어떤 기준으로 만들어지는지, Token ID는 왜 필요한지, ID가 어떻게 Vector와 연결되는지, 그 Vector는 어떻게 학습되는지, 그리고 최종적으로 어떤 형태의 Tensor가 Transformer에 입력되는지까지 살펴본다.


2.0 Text를 어떻게 계산 가능한 형태로 바꿀까?

지난 글에서 다음과 같은 Vector를 사용했다.

[0.18, -0.42, 0.71, 0.09, ...]

Vector라면 덧셈도 할 수 있고, Dot Product도 계산할 수 있다.

하지만 다음 문장은 그렇지 않다.

"나는 오늘 학교에 갔다."

컴퓨터가 이 Text 자체에 바로 Matrix Multiplication을 수행할 수는 없다.

따라서 언어를 계산 가능한 숫자 표현으로 변환해야 한다.

가장 먼저 떠올릴 수 있는 방법은 각 표현에 번호를 붙이는 것이다.

나는     → 3201
오늘     → 745
학교     → 2987
갔다     → 5021

하지만 여기서 3201은 "나는"의 의미를 나타내는 값이 아니다.

5021 > 745

라고 해서 "갔다"가 "오늘"보다 더 크거나 중요한 의미를 가진 것도 아니다.

이 숫자는 표현을 구분하기 위한 Identifier일 뿐이다.

따라서 실제로 필요한 과정은 다음과 같다.

Text
 ↓
Token
 ↓
Token ID
 ↓
Embedding Vector

그리고 첫 번째 질문은 자연스럽게 이것이 된다.

Text를 어떤 단위로 나눌 것인가?


2.1 Tokenizer와 Tokenization

Text를 모델이 처리할 수 있는 단위로 나누고 정수 ID로 연결하는 과정의 중심에 Tokenizer가 있다.

Text를 Token Sequence로 변환하는 과정을 Tokenization이라고 한다.

Tokenizer와 Tokenization은 서로 다른 개념이니 주의하자!

Tokenizer
= Tokenization을 수행하는 구성 요소/도구
Tokenization
= Text를 Token으로 변환하는 과정

예를 들어 설명을 위해 다음 문장을 생각해보자.

나는 오늘 학교에 갔다.

Tokenizer를 거치면 개념적으로 다음처럼 나뉠 수 있다.

["나는", "오늘", "학교", "에", "갔다", "."]

각각의 조각이 Token이다.

Raw Text
   ↓
Tokenizer
   ↓
Tokenization
   ↓
Token Sequence

여기서 가장 먼저 구분해야 하는 것이 있다.

Token ≠ 항상 Word

Token은 단어일 수도 있지만, 단어보다 작은 조각일 수도 있고 문자나 문장부호가 별도의 Token으로 처리될 수도 있다.

실제 Tokenization 결과는 사용하는 Tokenizer에 따라 달라진다.

그렇다면 왜 그냥 한 단어 = 한 Token으로 만들지 않는 걸까?


2.2 Word-level Tokenization의 한계

가장 직관적인 방법은 단어 단위로 Text를 나누는 것이다.

나는 오늘 학교에 갔다.

↓

나는 / 오늘 / 학교에 / 갔다

이를 Word-level Tokenization이라고 생각할 수 있다.

문제는 실제 언어에서 등장할 수 있는 단어의 형태가 매우 많다는 것이다.

한국어만 보더라도:

먹다
먹고
먹었다
먹었어요
먹었지만
먹겠습니다
먹으려고
먹으니까
...

처럼 하나의 어휘에서 다양한 형태가 만들어진다.

신조어와 고유명사도 계속 등장한다.

ChatGPT
ChatGPT가
ChatGPT에게
ChatGPT에서는
...

모든 가능한 표현을 독립적인 Token으로 Vocabulary에 넣으려고 하면 Vocabulary가 매우 커질 수 있다.

그리고 Vocabulary에 없는 표현이 등장하면 OOV(Out-of-Vocabulary) 문제가 발생할 수 있다.

새로운 표현
   ↓
Vocabulary에 없음
   ↓
OOV

전통적인 방식에서는 이런 표현을 <UNK> 같은 Unknown Token으로 처리하기도 했다.

하지만 서로 전혀 다른 미등록 단어들이 모두 같은 <UNK>로 바뀐다면 원래 Text에 있던 정보가 손실될 수 있다.

그렇다면 반대로 아주 작은 단위로 나누면 어떨까?


2.3 Character-level과 Subword Tokenization

예를 들어 문자 단위로 나눌 수 있다.

안녕하세요

↓

안 / 녕 / 하 / 세 / 요

이렇게 하면 필요한 기본 단위의 종류는 크게 줄어든다.

처음 보는 단어도 작은 단위들의 조합으로 표현할 가능성이 높아진다.

하지만 새로운 문제가 생긴다.

Sequence가 길어진다.

Word-level

큰 단위
↓
Token 수 ↓
Vocabulary Size ↑


Character-level

작은 단위
↓
Token 수 ↑
Vocabulary Size ↓

즉 다음과 같은 Trade-off가 존재한다.

Vocabulary Size
      ↕
Token Granularity
      ↕
Sequence Length

여기서 단어와 문자 사이의 절충안으로 등장하는 것이 Subword Tokenization이다.

핵심 아이디어는 다음과 같다.

자주 등장하는 표현은 비교적 큰 단위로 유지하고, 드물거나 새로운 표현은 더 작은 단위의 조합으로 표현한다.

예를 들어 개념적으로:

computer

→ ["computer"]


computerization

→ ["computer", "ization"]

처럼 처리할 수 있다.

실제 분리 결과는 Tokenizer와 Vocabulary에 따라 다르지만 핵심은 같다.

모든 단어를 Vocabulary에 저장하지 않고도 작은 단위들의 조합으로 다양한 Text를 표현할 수 있다.

대표적인 Subword 계열 방식에는 다음과 같은 것들이 있다.

Subword Tokenization
        │
        ├── BPE
        ├── WordPiece
        └── Unigram

이번 글에서는 이 중 BPE(Byte Pair Encoding)를 중심으로 원리를 이해해보자.


2.4 BPE — Subword는 어떻게 만들어질까?

BPE의 핵심 아이디어는 비교적 간단하다.

자주 함께 등장하는 인접 단위를 반복적으로 병합한다.

예를 들어 학습 Corpus에 다음 표현이 있다고 가정해보자.

low
lower
lowest

처음에는 작은 단위에서 시작한다.

l o w
l o w e r
l o w e s t

그리고 Corpus에서 인접한 Pair의 등장 빈도를 확인한다.

(l, o)
(o, w)
(w, e)
(e, r)
(e, s)
...

만약 (l, o)가 자주 등장한다면 두 단위를 하나로 병합한다.

l + o

↓

lo

그러면:

lo w
lo w e r
lo w e s t

가 된다.

다시 Pair의 빈도를 계산한다.

이번에는 (lo, w)가 자주 등장한다고 해보자.

lo + w

↓

low

결과적으로:

l / o / w

↓

lo / w

↓

low

처럼 반복되는 패턴이 점점 더 큰 단위가 된다.

이런 병합을 반복하면서 Tokenizer가 사용할 Subword와 Merge Rule을 만들어갈 수 있다.

핵심은:

빈번한 패턴
   ↓
더 큰 Token으로 병합

드문 패턴
   ↓
더 작은 Token의 조합으로 표현

하는 것이다.


2.5 Tokenizer Training ≠ 실제 Tokenization

BPE를 이해할 때 꼭 구분해야 하는 것이 있다.

Tokenizer를 학습하는 과정과 학습된 Tokenizer를 사용하는 과정은 다르다.

Tokenizer Training

Training Corpus
      ↓
빈도 분석
      ↓
반복적인 Merge
      ↓
Vocabulary
+
Merge Rules

이 단계에서 사용할 Token 집합과 Tokenization 규칙이 결정된다.

반면 실제 LLM을 사용할 때는:

New Text
   ↓
이미 준비된 Tokenizer
   ↓
Tokens

가 된다.

즉 사용자가 Prompt를 입력할 때마다 BPE가 새로운 Corpus 분석을 시작하고 Vocabulary를 다시 만드는 것이 아니다.

이미 학습·설정된 Tokenizer의 Vocabulary와 규칙을 이용해 새로운 Text를 Tokenize한다.


2.6 Vocabulary와 Token ID

Tokenizer가 사용할 수 있는 Token의 집합을 Vocabulary(Vocab)라고 한다.

개념적으로 다음처럼 생각할 수 있다.

Vocabulary

Token            ID
───────────────────
<pad>             0
<bos>             1
<eos>             2
나는              3
오늘              4
학교              5
에                6
갔다              7
.                 8
...

각 Token에는 고유한 정수 번호가 대응된다.

이 번호가 Token ID다.

따라서:

"나는 오늘 학교에 갔다."

        ↓ Tokenization

["나는", "오늘", "학교", "에", "갔다", "."]

        ↓ Vocabulary Lookup

[3, 4, 5, 6, 7, 8]

가 된다.

전체 흐름을 다시 보면:

Text
 ↓
Tokenizer
 ↓
Tokens
 ↓
Vocabulary
 ↓
Token IDs

여기서 반드시 기억해야 하는 것은:

Token ID = Identifier

Token ID ≠ Meaning

라는 점이다.

Token ID 5 자체에는 "학교"의 의미가 들어 있지 않다.

ID는 다음 단계에서 어떤 Vector를 가져올 것인지 찾기 위한 Index 역할을 한다.


2.7 Special Token — Text 외에도 Token이 필요하다

Vocabulary에는 일반 Text 조각 외에도 특별한 기능을 담당하는 Token이 포함될 수 있다.

이를 Special Token이라고 한다.

대표적으로 다음과 같은 역할이 있다.

Token대표적인 역할
BOSSequence의 시작 표시
EOSSequence의 종료 표시
PADBatch 등에서 Sequence 길이를 맞출 때 사용
UNKVocabulary에서 직접 표현할 수 없는 항목을 나타낼 때 사용

개념적으로:

<BOS>
나는
오늘
학교에
갔다
<EOS>

처럼 Sequence의 경계를 표현할 수도 있다.

다만 모든 모델이 동일한 이름과 동일한 Special Token 구조를 사용하는 것은 아니다.

특히 현대 Subword/byte 기반 Tokenizer에서는 미등록 Text를 더 작은 단위로 표현할 수 있기 때문에 <UNK>의 실제 필요성과 사용 방식도 Tokenizer에 따라 달라진다.

따라서 이름 자체를 암기하기보다는:

Vocabulary에는 자연어 조각뿐 아니라 모델의 입력 구조나 제어에 사용되는 특별한 Token도 존재할 수 있다.

라고 이해하는 것이 좋다.


2.8 Token ID는 숫자지만 아직 Vector가 아니다

여기까지 오면 Text는 숫자로 변했다.

"나는 오늘 학교에 갔다."

↓

[3, 4, 5, 6, 7, 8]

하지만 이 숫자들을 그대로 의미 계산에 사용할 수는 없다.

예를 들어:

오늘 = 4
학교 = 5

라고 해서:

학교 - 오늘 = 1

에는 의미가 없다.

Token ID는 Categorical Identifier에 가깝다.

하지만 0.1에서 살펴본 LLM의 연산에는 이런 형태가 필요했다.

학교

↓

[0.18, -0.42, 0.71, 0.09, ...]

여기서 Embedding이 등장한다.


2.9 Embedding — Token을 Vector 공간으로 옮기기

Embedding은 이산적인 Token을 연속적인 Vector Representation과 연결한다.

개념적으로:

"학교"
   ↓
Token ID = 5
   ↓
Embedding
   ↓
[0.18, -0.42, 0.71, 0.09, ...]

이렇게 얻은 Vector를 Token Embedding 또는 Embedding Vector라고 부를 수 있다.

그런데 여기서 중요한 질문이 생긴다.

ID 5를 어떤 공식으로 계산하면 저 Vector가 나오는 걸까?

단순히 5라는 숫자에 어떤 수학 공식을 적용하는 것이 아니다.

이 구조를 이해하려면 Embedding Matrix를 봐야 한다.


2.10 Embedding Matrix와 Lookup

Vocabulary Size를 (V), Embedding Dimension을 (d)라고 하자.

Embedding Matrix (E)는 개념적으로:

[
E \in \mathbb{R}^{V \times d}
]

의 형태를 가진다.

                Embedding Dimension (d)
              ─────────────────────────→

ID 0   [ 0.12, -0.31,  0.44, ... ]
ID 1   [-0.42,  0.11,  0.73, ... ]
ID 2   [ 0.21,  0.38, -0.17, ... ]
ID 3   [ 0.63, -0.19,  0.52, ... ]
ID 4   [-0.11,  0.27,  0.81, ... ]
ID 5   [ 0.18, -0.42,  0.71, ... ]
...
 ↑
Vocabulary Size (V)

Token ID가 5라면:

Token ID = 5
     ↓
Embedding Matrix
     ↓
5번 Row
     ↓
[0.18, -0.42, 0.71, ...]

를 가져온다.

이것이 Embedding Lookup이다.

즉:

Token ID
   │
   │ Index
   ▼
Embedding Matrix
   │
   │ Lookup
   ▼
Embedding Vector

라고 이해할 수 있다.

따라서 Token ID는 의미를 직접 담은 숫자가 아니라:

Embedding Matrix에서 어떤 Vector를 가져올지를 지정하는 Index

다.


2.11 Embedding Dimension은 무엇일까?

Embedding Vector에는 하나의 값이 아니라 여러 개의 값이 들어 있다.

[0.18, -0.42, 0.71, 0.09, ..., 0.31]

이 Vector가 몇 개의 숫자로 구성되는지를 Embedding Dimension이라고 한다.

예를 들어:

[0.2, 0.5, 0.7]

dimension = 3

이다.

실제 Transformer에서는 훨씬 높은 차원의 표현을 사용한다.

개념적으로:

Token
 ↓
Embedding
 ↓
d-dimensional Vector

Transformer 아키텍처에서는 이 표현의 크기가 모델의 hidden size 또는 d_model과 밀접하게 연결된다. 구체적인 구성은 모델 아키텍처에 따라 차이가 있을 수 있다.

중요한 것은:

Token 하나가 하나의 숫자로 표현되는 것이 아니라, 여러 숫자로 이루어진 고차원 Vector로 표현된다.

는 점이다.


2.12 Embedding Vector의 값은 누가 정할까?

그렇다면:

학교
 ↓
[0.18, -0.42, 0.71, ...]

에서 저 숫자들은 누가 정한 것일까?

사람이 각 Token의 의미를 분석해서 직접 값을 입력하는 것은 아니다.

Embedding Matrix는 모델의 Learnable Parameter다.

모델 학습 과정에서 다른 Parameter와 함께 값이 조정된다.

개념적으로 보면:

Token IDs
   ↓
Embedding Matrix
   ↓
Token Embeddings
   ↓
Transformer
   ↓
Prediction
   ↓
Loss
   ↓
Backpropagation
   ↓
Parameter Update
   ↓
Embedding Matrix Update

처음부터 완성된 의미 Vector가 들어 있는 것이 아니라 모델의 학습 목적에 따라 Prediction Error를 줄이는 방향으로 Embedding Matrix의 값도 조정된다.

따라서:

Embedding Matrix 역시 모델이 학습하는 Parameter의 일부다.

여기서는 Loss, Gradient, Backpropagation의 구체적인 계산까지 들어가지는 않는다.

이 과정은 이후 Training & Inference 편에서 다시 다룬다.


2.13 Word2Vec — Embedding을 이해하기 위한 중요한 배경

단어를 Vector로 표현한다는 아이디어는 Transformer에서 처음 등장한 것이 아니다.

대표적인 초기 분산 표현 학습 방법 중 하나가 Word2Vec이다.

핵심 아이디어를 단순화하면:

비슷한 문맥에서 등장하는 단어들이 유사한 Vector 표현을 갖도록 학습할 수 있다.

Word2Vec의 대표적인 학습 방식에는 CBOW와 Skip-gram이 있다.

CBOW

주변 단어를 이용해 중심 단어를 예측한다.

Context Words
      ↓
     CBOW
      ↓
Target Word

Skip-gram

중심 단어를 이용해 주변 단어를 예측한다.

Target Word
      ↓
  Skip-gram
      ↓
Context Words

여기서 중요한 것은 Word2Vec 알고리즘 자체를 외우는 것이 아니다.

언어의 의미적·분포적 관계를 Vector 공간에 표현할 수 있다는 Embedding의 직관을 이해하는 것이다.

그리고 이 개념은 Transformer에서 더 중요한 질문으로 이어진다.

같은 단어라도 문맥에 따라 의미가 달라진다면 Vector도 달라져야 하지 않을까?


2.14 Token Embedding과 Contextual Representation은 다르다

다음 두 문장을 보자.

I deposited money at the bank.

I sat on the bank of the river.

두 문장 모두 "bank"라는 표현을 포함하지만 의미는 다르다.

첫 번째는 금융기관이고 두 번째는 강둑이다.

여기서 Token Embedding과 Transformer를 거친 Contextual Representation을 구분해야 한다.

Token
 ↓
Token Embedding
 ↓
Transformer Layers
 ↓
Contextual Representation

Token Embedding은 Transformer가 계산을 시작하기 위한 초기 수치 표현이다.

Transformer에서는 주변 Token과의 관계가 계산된다.

첫 번째 문장에서는:

deposited
    │
 money ── bank

두 번째 문장에서는:

river
  │
bank ── sat

처럼 주변 Context가 다르다.

Transformer Layer를 거치면서 각 위치의 내부 표현은 주변 Token의 정보를 반영해 변화한다.

전통적인 Static Embedding과 비교하면 차이가 더 명확하다.

Static Word Embedding

Word
 ↓
Vector

동일한 Word
→ 기본적으로 동일한 학습된 표현


Transformer-based Model

Token
 ↓
Initial Token Embedding
 ↓
Transformer Layers
 ↓
Contextual Representation

동일한 Token이라도
문맥에 따라 내부 표현이 달라질 수 있음

따라서 중요한 구분은:

Embedding
≠
Context Understanding

이다.

Token Embedding은 문맥 이해의 최종 결과가 아니라 Transformer가 문맥 계산을 시작하기 위한 출발점이다.


2.15 Position Information — Vector만으로 충분할까?

이제 Token도 Vector로 변환했다.

그런데 또 하나의 정보가 필요하다.

다음 두 문장을 비교해보자.

고양이가 개를 쫓았다.

개가 고양이를 쫓았다.

비슷한 단어들이 등장하지만 의미는 다르다.

순서가 다르기 때문이다.

자연어에서는 Token의 위치가 의미를 결정하는 중요한 정보다.

Self-Attention 연산 자체에는 순서를 별도로 알려줄 필요가 있기 때문에 Transformer에는 Position Information이 필요하다.

개념적으로:

Token Embedding
       +
Position Information
       ↓
Transformer Input

초기 Transformer에서는 Sinusoidal Positional Encoding이 사용되었고, 모델에 따라 학습 가능한 Positional Embedding 같은 방식도 사용된다.

현대 LLM에서는 RoPE(Rotary Position Embedding)처럼 Attention 계산에 위치 정보를 반영하는 방식도 널리 사용된다.

여기서는 각각의 수학적 구현까지 깊게 들어가지는 않는다.

이번 단계에서 기억해야 할 것은:

Token이 무엇인지에 대한 정보뿐 아니라 Sequence 안에서 어디에 위치하는지에 대한 정보도 필요하다.

는 점이다.


2.16 Sequence Length와 Context Window

Token은 하나씩 독립적으로 입력되는 것이 아니라 순서대로 Sequence를 구성한다.

["나는", "오늘", "학교", "에", "갔다", "."]

Token ID로 보면:

[3, 4, 5, 6, 7, 8]

Token이 6개이므로:

Sequence Length = 6

이다.

즉 Sequence Length는 입력 Sequence를 구성하는 Token의 수다.

이 개념은 LLM을 실제로 사용할 때 자주 접하게 되는 Context Window와 연결된다.

Raw Text
   ↓
Tokenization
   ↓
Token Count
   ↓
Sequence Length
   ↓
Context Window

LLM은 무한한 길이의 Text를 한 번에 처리하지 않는다.

모델은 정해진 범위의 Context를 처리하도록 설계된다.

여기서 중요한 사실은:

글자 수 ≠ Token 수

단어 수 ≠ 항상 Token 수

라는 것이다.

Tokenizer와 언어, Text의 구성에 따라 같은 길이처럼 보이는 문장도 서로 다른 수의 Token으로 나뉠 수 있다.

그래서 LLM API나 모델 문서에서 다음과 같은 표현을 자주 보게 된다.

Input Tokens
Output Tokens
Context Length

결국 이 개념들도 Tokenization에서 출발한다.


2.17 Vector → Matrix → Tensor가 실제 LLM 입력이 되는 과정

여기서 지난 글의 Vector, Matrix, Tensor를 다시 가져와보자.

Embedding Dimension이 4라고 가정한다.

각 Token이 다음 Vector로 변환되었다고 해보자.

나는

[0.2, 0.7, 0.1, 0.4]


오늘

[0.5, 0.3, 0.8, 0.2]


학교

[0.9, 0.1, 0.4, 0.6]

Token 하나는 Vector다.

Token
 ↓
Embedding
 ↓
Vector

여러 Token Vector를 Sequence 순서대로 쌓으면:

[
 [0.2, 0.7, 0.1, 0.4],
 [0.5, 0.3, 0.8, 0.2],
 [0.9, 0.1, 0.4, 0.6]
]

Matrix가 된다.

Shape은:

[sequence_length, embedding_dimension]

↓

[3, 4]

이다.

여러 Sequence를 하나의 Batch로 묶으면 차원이 하나 더 추가된다.

[batch_size, sequence_length, embedding_dimension]

예를 들어:

[8, 128, 768]

이라면:

8   → Batch Size

128 → Sequence Length

768 → Embedding / Hidden Dimension

으로 읽을 수 있다.

즉 지난 글에서 배웠던 구조가 실제 LLM에서 이렇게 연결된다.

Vector
 ↓
Token 하나의 표현

Matrix
 ↓
Token Sequence의 표현

Tensor
 ↓
Batch 단위의 모델 입력

이제 Vector, Matrix, Tensor가 단순한 수학 개념이 아니라 실제 모델 안에서 어떤 데이터를 표현하는지 보이기 시작한다.


2.18 Code — Text에서 Token ID까지 확인하기

이제 실제 Tokenizer를 사용해보자.

Python의 transformers 라이브러리를 이용하면 모델과 함께 제공되는 Tokenizer를 직접 확인할 수 있다.

from transformers import AutoTokenizer

model_name = "bert-base-multilingual-cased"

tokenizer = AutoTokenizer.from_pretrained(model_name)

text = "나는 오늘 학교에 갔다."

tokens = tokenizer.tokenize(text)

token_ids = tokenizer.encode(
    text,
    add_special_tokens=False
)

print("Text:")
print(text)

print("\nTokens:")
print(tokens)

print("\nToken IDs:")
print(token_ids)

실제 Token과 ID는 사용하는 Tokenizer에 따라 달라진다.

여기서 확인하려는 것은 특정 숫자가 아니다.

구조다.

Text
 ↓
Tokenizer
 ↓
Tokens
 ↓
Token IDs

ID를 다시 Token으로 바꿔볼 수도 있다.

converted_tokens = tokenizer.convert_ids_to_tokens(token_ids)

print(converted_tokens)

이를 통해:

Token
 ↕
Vocabulary
 ↕
Token ID

의 대응 관계를 확인할 수 있다.


2.19 Code — Embedding Lookup 직접 확인하기

이번에는 PyTorch의 nn.Embedding을 사용해 Embedding Matrix와 Lookup 구조를 직접 확인해보자.

import torch
import torch.nn as nn

vocab_size = 10
embedding_dim = 4

embedding = nn.Embedding(
    num_embeddings=vocab_size,
    embedding_dim=embedding_dim
)

token_ids = torch.tensor([3, 4, 5])

vectors = embedding(token_ids)

print(vectors)
print(vectors.shape)

3개의 Token ID와 4차원 Embedding을 사용했으므로 결과 Shape은:

[3, 4]

가 된다.

3 Tokens
   ×
4 Dimensions

↓

[3, 4]

Embedding Matrix 자체도 확인할 수 있다.

print(embedding.weight.shape)

결과는:

[10, 4]

이다.

왜냐하면:

Vocabulary Size = 10

Embedding Dimension = 4

↓

Embedding Matrix Shape

[10, 4]

이기 때문이다.

이 코드의 핵심은 nn.Embedding의 문법을 외우는 것이 아니다.

실제 연산 구조를 확인하는 것이다.

Token ID
   ↓
Embedding Matrix
   ↓
Row Lookup
   ↓
Embedding Vector

2.20 전체 Pipeline — Text에서 Transformer Input까지

이제 처음 질문으로 돌아가보자.

"학교"

   ↓

   ?

   ↓

[0.18, -0.42, 0.71, ...]

이제 ? 안에 무엇이 있었는지 설명할 수 있다.

Raw Text
   │
   ▼
Tokenizer
   │
   ▼
Tokenization
   │
   ├── Word-level
   ├── Character-level
   └── Subword
         │
         └── BPE
   │
   ▼
Tokens
   │
   ▼
Vocabulary
   │
   ▼
Token IDs
   │
   ▼
Embedding Matrix
   │
   │ Lookup
   ▼
Token Embeddings
   │
   ├───────────────┐
   │               │
   │      Position Information
   │               │
   └───────┬───────┘
           ▼
   Input Representations
           │
           ▼
      Transformer

이번 과정을 크게 세 단계로 압축할 수도 있다.

Language를 나눈다

Text
 ↓
Tokenizer
 ↓
Tokens

Token을 식별한다

Tokens
 ↓
Vocabulary
 ↓
Token IDs

계산 가능한 표현으로 만든다

Token IDs
 ↓
Embedding Matrix
 ↓
Token Vectors
 ↓
Position Information
 ↓
Transformer Input

결국:

Language

↓

Discrete Representation

↓

Continuous Vector Representation

으로 변환되는 과정이다.


2.21 이번 글의 핵심 개념 정리

개념의미LLM에서의 역할
TokenizerText를 Token 단위로 처리하는 구성 요소Text → Model Input 변환
TokenizationText를 Token Sequence로 변환하는 과정입력 단위 생성
TokenTokenizer가 처리하는 기본 단위모델 입력의 기본 단위
Word-level단어 중심 Tokenization직관적이지만 큰 Vocabulary/OOV 문제 가능
Character-level문자 중심 Tokenization작은 Vocabulary, 긴 Sequence
Subword단어보다 작은 재사용 가능한 단위Vocabulary와 Sequence Length 사이의 균형
BPE빈번한 인접 단위를 반복적으로 병합Subword Vocabulary 구성
OOVVocabulary에 없는 표현Tokenization 설계가 해결해야 하는 문제 중 하나
VocabularyToken 집합과 ID 체계Token ↔ ID 연결
Token IDToken의 정수 식별자Embedding Lookup의 Index
Special Token특별한 기능을 담당하는 TokenSequence 구조 및 모델 제어
EmbeddingToken을 Vector Representation과 연결모델이 계산할 수 있는 입력 생성
Embedding MatrixToken별 Vector를 저장하는 학습 가능한 MatrixToken ID → Vector
Embedding LookupID에 해당하는 Row를 가져오는 과정Token Vector 생성
Embedding DimensionToken Vector의 차원Token 표현 크기
Learnable Parameter학습을 통해 값이 조정되는 ParameterEmbedding Matrix도 학습 대상
Word2Vec분산 단어 표현을 학습하는 대표적 방법Embedding 개념의 배경
Position InformationSequence에서 Token의 위치 정보Token 순서 반영
Sequence LengthSequence를 구성하는 Token 수입력 크기
Context Window모델이 처리할 수 있는 Context 범위Token 입력·출력 범위와 연결
Contextual Representation문맥이 반영된 내부 Vector 표현Transformer Layer를 통해 형성

2.22 다음 질문 — Vector가 되었는데 문맥은 어디에 있을까?

이제 Text를 Vector로 바꾸는 과정까지 왔다.

Text
 ↓
Tokenization
 ↓
Token
 ↓
Token ID
 ↓
Embedding
 ↓
Vector

하지만 Vector가 만들어졌다고 해서 LLM이 문맥을 이해한 것은 아니다.

다시 두 문장을 보자.

I deposited money at the bank.

I sat on the bank of the river.

bank가 어떤 의미인지를 판단하려면 주변 Token과의 관계를 봐야 한다.

       deposited
           │
money ── bank


          river
            │
        bank ── sat

그렇다면 다음 질문은:

하나의 Token Vector가 다른 Token Vector와 어떤 관계를 갖는지 어떻게 계산할까?

가 된다.

여기서 지난 글의 Dot Product가 다시 등장한다.

Vector A
   │
   │ Dot Product
   │
Vector B
   ↓
Relation Score

그리고 Transformer에서는 이 아이디어가 Query, Key, Value와 만나 Attention으로 이어진다.

Token Embeddings
       ↓
Query · Key
       ↓
Attention Score
       ↓
Softmax
       ↓
Attention Weight
       ↓
Value
       ↓
Contextual Representation

지난 글에서 배운 Dot Product와 Softmax가 왜 필요했는지도 이제 드러나기 시작한다.


다음 글

3. LLM은 문맥을 어떻게 계산할까? — Attention & Transformer

다음 글에서는 Transformer의 핵심으로 들어간다.

왜 RNN만으로는 부족했을까?

        ↓

Attention은 무엇을 해결했을까?

        ↓

Query · Key · Value

        ↓

Scaled Dot-Product Attention

        ↓

Self-Attention

        ↓

Multi-Head Attention

        ↓

Transformer Block

        ↓

Contextual Representation

그리고 지금까지의 세 글이 하나의 흐름으로 연결된다.

1. Vector & NumPy
"Vector를 어떻게 계산하는가?"

            ↓

2. Tokenization & Embedding
"Language를 어떻게 Vector로 만드는가?"

            ↓

3. Attention & Transformer
"Vector 사이의 관계로 Context를 어떻게 만드는가?"

우리는 이제 LLM의 전체 흐름 중:

Language → Number → Context → Generation

에서

Language → Number

까지 왔다.

다음은 Number → Context다.

0개의 댓글