Loading the catalog…
Loading the catalog…
Transformer는 RNN처럼 Token을 순서대로 처리하지 않고, Attention 을 이용해 시퀀스 전체의 관계를 한 번에 계산한다. AutoEncoder는 입력 데이터를 압축한 뒤 다시 복원하면서 데이터의 핵심 특징을 학습한다. 이번에는 Transformer의 Multi-Head Attention, 위치 정보, Encoder와 Decoder 구조를 살펴본다. 이어서 MNIST 이미지에 합성곱 AutoEncoder를 적용해 원본 이미지를 복원하는 과정을 정리한다. 1. Padding Mask 배치 학습에서는 길이가 서로 다른 문장을 하나의 Tensor로 묶기 위해 짧은 문장 뒤에 <PAD> Token을 추가한다. 커피 한잔 어때 안녕 <PAD> <PAD> <PAD> 는 길이를 맞추기 위한 자리일 뿐 실제 의미가 없다. Attention이 이 위치를 참고하면 의미 없는 정보가 문맥에 섞이므로 Padding Mask 로 제외해야 한다. import torch sample_scores = torch.tensor([[2.0, 1.0, 0.5, 3.0]]) padding_mask = torch.tensor([1, 1, 1, 0]) masked_scores = sample_scores.masked_fill( padding_mask == 0, float("-inf"), ) weights_before = torch.softmax(sample_scores, dim=-1) weights_after = torch.softmax(masked_scores, dim=-1) print("Mask 전:", weights_before) print("Mask 후:", weights_after) 실행 결과: Mask 전: tensor([[0.2321, 0.0854, 0.0518, 0.6308]]) Mask 후: tensor([[0.6285, 0.2312, 0.1402, 0.0000]]) Mask가 적용된 Score는 -inf 가 된다. softmax(-inf) 의 결과는 0 이므로 해당 위치의 Value가 최종 출력에 반영되지 않는다. Score: [2.0, 1.0, 0.5, -inf] ↓ Softmax Weight: [0.6285, 0.2312, 0.1402, 0.0000] 2. Transformer Transformer는 2017년 논문 Attention Is All You Need 에서 제안된 신경망 구조다. RNN 계열 모델은 이전 시점의 계산이 끝나야 다음 시점을 처리할 수 있다. Transformer는 Self-Attention으로 모든 Token의 관계를 직접 계산하므로 병렬 연산에 유리하며, 멀리 떨어진 Token 사이의 관계도 직접 연결할 수 있다. RNN x1 → h1 → x2 → h2 → x3 → h3 Transformer x1 ─┬─ x1, x2, x3과 관계 계산 x2 ─┼─ x1, x2, x3과 관계 계산 x3 ─┴─ x1, x2, x3과 관계 계산 대표적인 Transformer 기반 모델에는 BERT, GPT, T5 등이 있다. 3. Multi-Head Attention Self-Attention을 Q, K, V 한 세트로만 계산하면 하나의 표현 공간에서 관계를 학습한다. Multi-Head Attention 은 여러 Head가 서로 다른 Q, K, V 투영을 학습해 다양한 관계를 동시에 포착하도록 만든다. 학습 결과에 따라 어떤 Head는 가까운 Token 관계에, 다른 Head는 문법적 관계나 의미적 관계에 반응할 수 있다. 각 Head의 역할을 사람이 미리 지정하는 것은 아니다. d_model = 512 num_heads = 8 head_dim = 512 / 8 = 64 d_model 은 num_heads 로 나누어떨어져야 한다. PyTorch로 확인하기 import torch import torch.nn as nn embed_dim = 8 num_heads = 2 head_dim = embed_dim // num_heads print("전체 Embedding 차원:", embed_dim) print("Head 개수:", num_heads) print("Head 하나의 차원:", head_dim) # batch=1, token=3, embed_dim=8 x = torch.randn(1, 3, embed_dim) mha = nn.MultiheadAttention( embed_dim=embed_dim, num_heads=num_heads, batch_first=True, ) attn_output, attn_weights = mha( query=x, key=x, value=x, need_weights=True, ) print("입력 Shape:", x.shape) print("출력 Shape:", attn_output.shape) print("Attention Weight Shape:", attn_weights.shape) 실행 결과: 전체 Embedding 차원: 8 Head 개수: 2 Head 하나의 차원: 4 입력 Shape: torch.Size([1, 3, 8]) 출력 Shape: torch.Size([1, 3, 8]) Attention Weight Shape: torch.Size([1, 3, 3]) 입력과 출력의 마지막 차원은 모두 8 이다. 내부에서는 두 Head가 각각 4차원 공간에서 Attention을 계산한 뒤, 결과를 합쳐 다시 8차원 표현을 만든다. Head별 Attention Weight 확인하기 nn.MultiheadAttention 은 기본적으로 여러 Head의 Weight를 평균해 반환한다. [batch, target_length, source_length] average_attn_weights=False 를 지정하면 Head별 Weight를 따로 확인할 수 있다. attn_output, head_weights = mha( query=x, key=x, value=x, need_weights=True, average_attn_weights=False, ) print(head_weights.shape) print("Head 0:\n", head_weights[0, 0]) print("Head 1:\n", head_weights[0, 1]) 실행 결과: torch.Size([1, 2, 3, 3]) 차원의 의미는 다음과 같다. [batch, num_heads, target_length, source_length] [1, 2, 3, 3] Self-Attention에서는 Query와 Key가 같은 시퀀스에서 만들어지므로 보통 target_length 와 source_length 가 같다. Attention Weight 시각화 세로축은 정보를 찾는 Query Token이다. 가로축은 참고 대상인 Key Token이다. 색이 밝을수록 해당 Key를 더 많이 참고한다. Weight는 학습 과정에서 계속 달라지므로 한 번 출력한 값만으로 Head의 역할을 단정해서는 안 된다. 4. Transformer Encoder 전체 흐름 Transformer Encoder의 한 Block은 다음 순서로 동작한다. 문장 ↓ Tokenization ↓ Token ID ↓ Token Embedding + Positional Encoding ↓ Multi-Head Self-Attention ↓ Residual Connection + LayerNorm ↓ Feed Forward Network ↓ Residual Connection + LayerNorm ↓ Encoder 출력 각 단계는 입력과 출력의 Shape을 유지하면서 Token 표현을 점차 문맥에 맞게 바꾼다. 5. Token Embedding 먼저 Token을 정수 ID로 바꾼다. vocab = { "<PAD>": 0, "커피": 1, "한잔": 2, "어때": 3, } tokens = ["커피", "한잔", "어때"] token_ids = torch.tensor([[1, 2, 3]]) print(token_ids) print(token_ids.shape) tensor([[1, 2, 3]]) torch.Size([1, 3]) nn.Embedding 은 각 Token ID를 d_model 차원의 실수 벡터로 바꾼다. vocab_size = len(vocab) d_model = 8 embedding = nn.Embedding( vocab_size, d_model, padding_idx=0, ) X = embedding(token_ids) print(X.shape) torch.Size([1, 3, 8]) Shape은 다음처럼 변한다. [batch, sequence_length] [1, 3] ↓ Embedding [batch, sequence_length, d_model] [1, 3, 8] 6. Positional Encoding Self-Attention은 모든 Token의 관계를 한 번에 계산하므로 RNN처럼 계산 순서에서 위치 정보를 얻지 못한다. 따라서 입력에 Token의 위치 정보를 별도로 넣어야 한다. 원래 Transformer 논문은 고정된 Sin과 Cos 함수를 이용한 Positional Encoding을 사용한다. 최종 입력 = Token Embedding + Positional Encoding 예를 들어 다음처럼 같은 차원의 두 벡터를 더한다. Token Embedding: [0.20, -0.40, 0.70, ...] Position 0 Encoding: [0.00, 1.00, 0.00, ...] 최종 입력: [0.20, 0.60, 0.70, ...] 모델이 각 숫자를 Token 정보와 위치 정보로 따로 읽는 것은 아니다. 같은 d_model 공간에서 두 벡터를 더해 의미와 위치가 함께 반영된 표현 을 만든다. Sin/Cos Positional Encoding 구현 import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=100): super().__init__() position = torch.arange( max_len, dtype=torch.float32, ).unsqueeze(1) div_term = torch.exp( torch.arange( 0, d_model, 2, dtype=torch.float32, ) * (-math.log(10000.0) / d_model) ) pe = torch.zeros(max_len, d_model) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) self.register_buffer("pe", pe.unsqueeze(0)) def forward(self, x): seq_len = x.size(1) return x + self.pe[:, :seq_len] pe 는 학습으로 바뀌는 Parameter가 아니라 공식으로 계산한 고정값이다. register_buffer() 로 등록하면 다음 특성을 얻는다. model.to(device) 를 호출할 때 모델과 함께 GPU나 MPS로 이동한다. state_dict() 에 포함되어 저장된다. Optimizer의 학습 대상 Parameter에는 포함되지 않는다. pos_encoding = PositionalEncoding(d_model) X_pos = pos_encoding(X) print("Embedding:", X.shape) print("Position 추가:", X_pos.shape) Embedding: torch.Size([1, 3, 8]) Position 추가: torch.Size([1, 3, 8]) 위치 정보를 더해도 Shape은 변하지 않는다. RoPE RoPE(Rotary Positional Embedding)는 위치 벡터를 입력 Embedding에 단순히 더하지 않는다. Attention에 사용하는 Q와 K를 Token 위치에 따라 회전시켜 상대적인 위치 관계가 점수에 반영되도록 만든다. Sin/Cos Positional Encoding Embedding에 위치 벡터를 더한다. RoPE Q와 K의 Attention 계산에 위치 관계를 반영한다. 7. Multi-Head Self-Attention 적용 위치 정보가 더해진 X_pos 를 Query, Key, Value에 모두 사용하면 Self-Attention이 된다. num_heads = 2 mha = nn.MultiheadAttention( embed_dim=d_model, num_heads=num_heads, batch_first=True, ) attn_out, attn_weights = mha( query=X_pos, key=X_pos, value=X_pos, need_weights=True, average_attn_weights=False, ) print("입력:", X_pos.shape) print("출력:", attn_out.shape) print("Head별 Weight:", attn_weights.shape) 실행 결과: 입력: torch.Size([1, 3, 8]) 출력: torch.Size([1, 3, 8]) Head별 Weight: torch.Size([1, 2, 3, 3]) Attention은 Token 사이의 정보를 섞지만 [batch, sequence_length, d_model] Shape은 유지한다. 8. Residual Connection Residual Connection은 Attention 결과만 다음 단계로 보내지 않고 원래 입력을 다시 더하는 구조다. y = x + F(x) x 는 Attention에 들어가기 전의 원래 정보다. F(x) 는 Attention이 계산한 변화 정보다. x + F(x) 는 원래 정보를 유지하면서 새로운 문맥 정보를 추가한 결과다. residual = X_pos + attn_out print(residual.shape) torch.Size([1, 3, 8]) 원소별 덧셈을 수행하므로 x 와 F(x) 의 Shape이 같아야 한다. Residual Connection은 깊은 신경망에서 정보와 Gradient가 전달될 경로를 제공해 학습을 돕는다. 9. Layer Normalization nn.LayerNorm(d_model) 은 각 Token 벡터의 Feature 차원을 기준으로 값을 정규화한다. 입력 Shape이 [batch, sequence_length, d_model] 이면 각 Token의 (d_model,) 벡터를 독립적으로 정규화한다. norm1 = nn.LayerNorm(d_model) x1 = norm1(residual) print("평균:", x1[0, 0].mean()) print("분산:", x1[0, 0].var(unbiased=False)) 실행 결과: 평균: tensor(0.) 분산: tensor(1.000) LayerNorm은 각 샘플과 Token 안에서 정규화하므로 BatchNorm보다 배치 크기의 영향을 덜 받는다. 가변 길이 시퀀스를 다루는 Transformer에 잘 맞는다. 10. Feed Forward Network Attention과 FFN은 서로 다른 역할을 한다. 구성 요소 역할 Attention 다른 Token에서 어떤 정보를 가져올지 계산한다. FFN 모은 정보를 각 Token 위치에서 독립적으로 비선형 변환한다. ffn = nn.Sequential( nn.Linear(d_model, 32), nn.ReLU(), nn.Linear(32, d_model), ) ffn_out = ffn(x1) print("입력:", x1.shape) print("출력:", ffn_out.shape) 입력: torch.Size([1, 3, 8]) 출력: torch.Size([1, 3, 8]) 동일한 FFN이 모든 Token 위치에 적용되지만, 각 Token은 다른 Token과 섞이지 않고 자신의 Feature만 변환한다. Encoder Block에는 두 개의 Sub-layer가 있으며 각각 Residual Connection과 LayerNorm이 붙는다. Self-Attention ↓ Residual + LayerNorm ↓ FFN ↓ Residual + LayerNorm norm2 = nn.LayerNorm(d_model) encoder_output = norm2(x1 + ffn_out) print(encoder_output.shape) torch.Size([1, 3, 8]) 11. Encoder에서 Padding Mask 사용하기 PyTorch의 nn.MultiheadAttention 은 key_padding_mask 에서 다음 규칙을 사용한다. False: 참고할 수 있는 실제 Token True: 참고 대상에서 제외할 PAD Token padded_ids = torch.tensor([ [1, 2, 3], [1, 3, 0], ]) padding_mask = padded_ids.eq(0) print(padded_ids) print(padding_mask) 실행 결과: tensor([[1, 2, 3], [1, 3, 0]]) tensor([[False, False, False], [False, False, True]]) Mask를 Multi-Head Attention에 전달한다. padded_x = pos_encoding(embedding(padded_ids)) masked_out, masked_weights = mha( padded_x, padded_x, padded_x, key_padding_mask=padding_mask, need_weights=True, ) print(masked_weights[1]) 두 번째 문장의 Attention Weight는 다음과 같다. tensor([[0.392, 0.608, 0.000], [0.372, 0.628, 0.000], [0.414, 0.586, 0.000]]) 마지막 열은 <PAD> 가 있는 Key 위치다. 모든 Query에서 가중치가 0 이므로 참고 대상에서 제외된 것을 확인할 수 있다. key_padding_mask 는 PAD를 Key와 Value의 참고 대상에서 제외한다. PAD 위치의 Query 출력까지 자동으로 삭제하는 것은 아니므로, 이후 Loss를 계산할 때도 PAD 위치를 제외해야 한다. 12. Causal Mask Padding Mask와 Causal Mask는 목적이 다르다. Mask 가리는 대상 사용 목적 Padding Mask 의미 없는 <PAD> 위치 길이가 다른 문장을 배치로 처리한다. Causal Mask 현재보다 뒤에 있는 미래 Token 다음 Token의 정답 누출을 막는다. 문장 나는 커피를 마신다 를 학습할 때 각 위치가 볼 수 있는 범위는 다음과 같다. Query | 나는 | 커피를 | 마신다 -------|------|--------|------- 나는 | O | X | X 커피를 | O | O | X 마신다 | O | O | O 추론할 때는 Token을 하나씩 생성하지만, 학습할 때는 정답 문장 전체를 알고 있다. Mask 없이 병렬 계산하면 앞쪽 위치가 미래의 정답 Token을 미리 볼 수 있으므로 Causal Mask가 필요하다. seq_len = 4 causal_mask = torch.triu( torch.ones(seq_len, seq_len, dtype=torch.bool), diagonal=1, ) print(causal_mask) tensor([[False, True, True, True], [False, False, True, True], [False, False, False, True], [False, False, False, False]]) True 인 오른쪽 위 영역이 미래 Token에 해당한다. 실제 Attention Weight는 다음처럼 미래 위치가 0 이 된다. tensor([[1.000, 0.000, 0.000, 0.000], [0.640, 0.360, 0.000, 0.000], [0.315, 0.311, 0.374, 0.000], [0.166, 0.159, 0.303, 0.372]]) 13. Transformer Encoder Block 구현 지금까지 확인한 구성 요소를 하나의 Encoder Block으로 묶는다. class SimpleTransformerEncoderBlock(nn.Module): def __init__( self, d_model=8
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[71일차] Transformer 구조와 AutoEncoder. Transformer는 RNN처럼 Token을 순서대로 처리하지 않고, Attention 을 이용해 시퀀스 전체의 관계를 한 번에 계산한다. AutoEncoder는 입력 데이터를 압축한 뒤 다시 복원하면서 데이터의 핵심 특징을 학습한다. 이번에는 Transformer의 Multi-Head Attention, 위치 정보, Encoder와 Decoder 구조를 살펴본다. 이어서 MNIST 이미지에 합성곱 AutoEncoder를 적용해 원본 이미지를 복원하는 과정을 정리한다. 1. Padding Mask 배치 학습에서는 길이가 서로 다른 문장을 하나의 Tensor로 묶기 위해 짧은 문장 뒤에 Token을 추가한다. 커피 한잔 어때 안녕 는 길이를…
Open source