1. 트랜스포머
트랜스포머(Transformer)는 2017년 구글 논문(Attention is all you need)에서 발표된 자연어 처리(NLP)와 다양한 시퀀스 학습 문제에서 뛰어난 성능을 보이는 신경망 아키텍처로, 어텐션 메커니즘을 활용하여 병렬 연산이 가능하고 장기 의존성을 효과적으로 처리할 수 있습니다. 기존의 순환신경망(RNN)과 달리 순차적인 계산 없이 전체 입력 문장을 한 번에 처리하며, 특히 "셀프 어텐션(Self-Attention)"을 사용하여 문장 내 단어 간 관계를 동적으로 학습합니다. 대표적인 트랜스포머 기반 모델로는 BERT, GPT, T5 등이 있으며, 머신 번역, 텍스트 생성, 문서 요약 등 다양한 AI 분야에서 널리 활용되고 있습니다.
2. 트랜스포머의 특징
- 순차 처리 없이 병렬 연산
트랜스포머는 RNN/LSTM처럼 토큰을 한 개씩 순서대로 처리하지 않고, 전체 시퀀스를 한 번에 행렬 연산으로 처리합니다. 재귀 의존이 없어서 GPU 병렬화가 잘 되고, 길이가 길어도 학습·추론 속도가 빠르며 장기 의존성도 손실 없이 다루기 쉽습니다. - 셀프 어텐션(Self-Attention)
각 토큰이 다른 모든 토큰을 ‘얼마나 볼지’를 가중치로 계산해, 중요한 단어는 크게, 덜 중요한 단어는 작게 반영해 새로운 표현을 만듭니다. 직관적으로는 Q/K/V(쿼리/키/값)로 유사도를 구해 해당 내용(V)을 가중합하는 방식이며, “사과”처럼 문맥에 따라 달라지는 의미를 스스로 연결해 파악합니다. - 멀티-헤드 어텐션(Multi-Head)
한 번의 어텐션만으로는 다양한 관계를 모두 포착하기 어렵기 때문에 여러 개의 ‘헤드’가 서로 다른 관점(문법, 의미, 상호참조 등)에서 동시에 주의를 분배합니다. 각 헤드가 독립적으로 어텐션을 계산해 특성 차원을 나눠 보고, 마지막에 합쳐 더 풍부한 문맥 표현을 얻습니다. - 포지셔널 인코딩(Positional Encoding)
트랜스포머는 순차 처리 구조가 아니므로 위치 정보를 따로 넣어줘야 합니다. 임베딩에 위치 신호(사인·코사인 기반 또는 학습형)를 더해 토큰의 순서를 인지하게 만들며, “나는 학교에 간다”와 “학교에 나는 간다”처럼 순서가 다른 문장을 구분할 수 있게 합니다. - 디코더 마스킹(Masking)
생성 시 디코더가 미래 토큰을 미리 보지 못하도록 ‘원인-결과’ 방향(좌→우)으로 가리는 캐주얼 마스크를 적용합니다. 동시에 패딩 토큰을 무시하는 패딩 마스크도 함께 써서 불필요한 계산과 학습 교란을 방지합니다. 이렇게 하면 자연스러운 다음 단어 예측이 가능합니다. - 대규모 사전학습과 강력한 성능
트랜스포머는 대용량 말뭉치로 사전학습(Pretraining)한 뒤 작업별 미세조정(Fine-tuning)에 특히 강합니다. BERT, GPT, T5 등 거대 모델이 이 구조를 기반으로 하며, 긴 문맥 이해와 정확한 생성에서 RNN 계열의 한계를 크게 넘어서는 성능을 보입니다.

3. 포지셔널 인코딩
포지셔널 인코딩은 트랜스포머가 문장을 병렬로 처리하면서도 단어의 순서와 거리를 인식하도록 각 토큰 임베딩에 위치 신호 벡터를 더하는 기법입니다. 특히 사인·코사인 기반 정적 인코딩은 다양한 주파수의 파형을 조합해 위치마다 고유한 패턴을 부여하고, 이 패턴 간 유사도를 통해 토큰 사이의 상대적 거리와 순서를 추론할 수 있게 합니다. 이 방식은 별도의 학습 파라미터가 없어 재현성이 높고 과적합 위험이 낮으며, 학습 시 보지 않은 더 긴 시퀀스에도 자연스럽게 일반화되는 장점이 있습니다. 구현은 간단히 이 위치 신호를 토큰 임베딩과 요소별로 더해주면 되며, 이를 통해 모델은 “나는 학교에 간다”와 “학교에 나는 간다”처럼 단어 구성은 같아도 순서가 다른 문장을 구분할 수 있습니다.


정리
트랜스포머는 문장의 순서를 직접 알 수 없기 때문에, 각 단어의 위치를 알려주는 포지셔널 인코딩을 사용합니다. 이때 사인과 코사인을 쌍으로 쓰는 이유는 위치를 유일하게 표현하고, 상대적 거리(몇 칸 떨어져 있는지)를 단순한 선형 계산으로 쉽게 구할 수 있기 때문입니다. 또한 파장이 다른 여러 주기(짧은 파장과 긴 파장)를 동시에 쓰면 가까운 단어 순서도 잘 구분하면서, 멀리 떨어진 단어의 위치도 겹치지 않고 표현할 수 있습니다. 쉽게 말해 여러 개의 “서로 다른 속도로 도는 시계”를 한꺼번에 사용해 각 위치를 고유하게 나타내는 것이라 보면 됩니다.
4. 정규화
트랜스포머(Transformer)에서는 정규화(Normalization)가 안정적인 학습과 빠른 수렴을 위해 중요한 역할을 합니다. 대표적으로 레이어 정규화(Layer Normalization)가 사용되는데, 이는 한 개의 입력 샘플 내부의 모든 뉴런 값에 대해 평균과 분산을 계산하여 정규화하는 방식입니다. 즉, 각 배치의 크기와 무관하게 특징(feature) 차원별로 정규화하므로, 시퀀스 길이가 달라져도 안정적으로 작동합니다. 반면에 배치 정규화(Batch Normalization)는 미니배치 단위로 평균과 분산을 계산하여 정규화하기 때문에 CNN 같은 이미지 처리에서는 효과적이지만, 트랜스포머처럼 입력 시퀀스 길이가 가변적이고 순서에 민감한 모델에서는 덜 적합합니다. 따라서 트랜스포머에서는 주로 레이어 정규화를 사용하며, 이를 통해 각 층의 출력 분포를 일정하게 유지하여 기울기 소실이나 폭발을 방지하고, 학습 속도를 높이는 효과를 얻습니다.



정리
트랜스포머는 배치정규화(BatchNorm) 대신 레이어정규화(LayerNorm)를 사용합니다. 배치정규화는 같은 배치 안의 여러 샘플을 기준으로 평균과 분산을 계산하기 때문에 배치 크기가 작거나 문장 길이가 가변적인 NLP 환경에서는 불안정해질 수 있고, 훈련과 추론 시 동작 방식도 달라집니다. 반면 레이어정규화는 샘플 하나(토큰 하나)의 채널 축만을 기준으로 평균과 분산을 내어 정규화하기 때문에 배치 크기와 무관하게 항상 같은 방식으로 동작하며, 패딩이나 마스킹에도 영향을 덜 받아 안정적입니다. 쉽게 말해 BatchNorm은 "반 전체 평균으로 내 점수를 보정"하는 방식이고, LayerNorm은 "내 과목 평균으로 내 점수를 보정"하는 방식이라 할 수 있으며, 이런 이유로 트랜스포머에서는 LayerNorm이 표준적으로 사용됩니다.
5. 피드포워드 신경망과 잔차 연결
트랜스포머에서 피드포워드 신경망(Feed-Forward Neural Network, FFN) 은 어텐션 레이어 뒤에 위치하여 각 단어 벡터를 개별적으로 비선형 변환하는 역할을 합니다. 보통 두 개의 선형 계층과 ReLU나 GELU 같은 활성화 함수를 사용하며, 먼저 차원을 확장한 뒤 다시 원래 차원으로 축소합니다(예: 512 → 2048 → 512). 이 과정은 어텐션이 문맥적 관계를 학습하는 데 집중하는 반면, 피드포워드 신경망은 단어별 표현을 더 복잡하게 가공하여 모델의 비선형성과 표현력을 높여줍니다. 따라서 FFN은 어텐션이 잡아낸 문맥 정보를 강화하고, 각 단어 임베딩을 더 풍부하게 만드는 핵심 구성 요소입니다.


import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D, LayerNormalization, Dropout, Add, Input
from tensorflow.keras.optimizers import Adam
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
# CSV 파일 로드
dataframe = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/sentiment_data.csv')
# 데이터와 라벨 추출
sentences = dataframe['sentence'].tolist()
labels = dataframe['label'].tolist()
# 임베딩 벡터 크기와 최대 문장 길이 설정
embedding_dim = 128
max_len = 10
# 토크나이저 초기화 및 텍스트를 시퀀스로 변환
tokenizer = tf.keras.preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(sentences)
sequences = tokenizer.texts_to_sequences(sentences)
word_index = tokenizer.word_index
# 패딩을 사용하여 시퀀스 길이를 동일하게 맞춤
data = tf.keras.preprocessing.sequence.pad_sequences(sequences, maxlen=max_len, padding='post')
# 데이터셋을 훈련 세트와 검증 세트로 분리
X_train, X_val, y_train, y_val = train_test_split(data, labels, test_size=0.2, random_state=42)
# 포지셔널 인코딩 함수
def get_positional_encoding(max_len, d_model):
pos_enc = np.zeros((max_len, d_model)) # 포지셔널 인코딩 배열 초기화. max_len: 최대 시퀀스 길이, d_model:임베딩 벡터의 차원
# 시퀀스의 각 위치에 대해 포지셔널 인코딩 값 계산
for pos in range(max_len):
for i in range(0, d_model, 2):
pos_enc[pos, i] = np.sin(pos / (10000 ** (2 * i / d_model))) # 짝수 인덱스
if i + 1 < d_model:
pos_enc[pos, i + 1] = np.cos(pos / (10000 ** (2 * (i + 1) / d_model))) # 홀수 인덱스
return pos_enc
# 포지셔널 인코딩 생성
positional_encoding = get_positional_encoding(max_len, embedding_dim)
# 멀티헤드 어텐션 레이어
class MultiHeadSelfAttentionLayer(tf.keras.layers.Layer):
def __init__(self, num_heads, key_dim):
super(MultiHeadSelfAttentionLayer, self).__init__() # 레이어가 생성될 때 한 번 실행.
self.mha = tf.keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=key_dim) # 멀티헤드 어텐션 레이어 생성. num_heads: 어텐션 헤드의 수, key_dim은 각 헤드의 차원 수
self.norm = LayerNormalization() #레이어 정규화
def call(self, x):
attn_output = self.mha(query=x, value=x, key=x) # 멀티헤드 어텐션 적용
attn_output = self.norm(attn_output + x) # 잔차 연결 적용
return attn_output
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D, LayerNormalization, Dropout, Add, Input
from tensorflow.keras.optimizers import Adam
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
# CSV 파일 로드
dataframe = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/sentiment_data.csv')
# 데이터와 라벨 추출
sentences = dataframe['sentence'].tolist()
labels = dataframe['label'].tolist()
# 임베딩 벡터 크기와 최대 문장 길이 설정
embedding_dim = 128
max_len = 10
# 토크나이저 초기화 및 텍스트를 시퀀스로 변환
tokenizer = tf.keras.preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(sentences)
sequences = tokenizer.texts_to_sequences(sentences)
word_index = tokenizer.word_index
# 패딩을 사용하여 시퀀스 길이를 동일하게 맞춤
data = tf.keras.preprocessing.sequence.pad_sequences(sequences, maxlen=max_len, padding='post')
# 데이터셋을 훈련 세트와 검증 세트로 분리
X_train, X_val, y_train, y_val = train_test_split(data, labels, test_size=0.2, random_state=42)
# 포지셔널 인코딩 함수
def get_positional_encoding(max_len, d_model):
pos_enc = np.zeros((max_len, d_model)) # 포지셔널 인코딩 배열 초기화. max_len: 최대 시퀀스 길이, d_model:임베딩 벡터의 차원
# 시퀀스의 각 위치에 대해 포지셔널 인코딩 값 계산
for pos in range(max_len):
for i in range(0, d_model, 2):
pos_enc[pos, i] = np.sin(pos / (10000 ** (2 * i / d_model))) # 짝수 인덱스
if i + 1 < d_model:
pos_enc[pos, i + 1] = np.cos(pos / (10000 ** (2 * (i + 1) / d_model))) # 홀수 인덱스
return pos_enc # 포지셔널 인코딩 값을 반환
# 포지셔널 인코딩 생성
positional_encoding = get_positional_encoding(max_len, embedding_dim)
# 멀티헤드 어텐션 레이어
class MultiHeadSelfAttentionLayer(tf.keras.layers.Layer):
def __init__(self, num_heads, key_dim):
super(MultiHeadSelfAttentionLayer, self).__init__() # 레이어가 생성될 때 한 번 실행.
self.mha = tf.keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=key_dim) # 멀티헤드 어텐션 레이어 생성. num_heads: 어텐션 헤드의 수, key_dim은 각 헤드의 차원 수
self.norm = LayerNormalization() #레이어 정규화
def call(self, x):
attn_output = self.mha(query=x, value=x, key=x) # 멀티헤드 어텐션 적용
attn_output = self.norm(attn_output + x) # 잔차 연결 적용 후 레이어 정규화
return attn_output
# 모델 설정
inputs = Input(shape=(max_len,))
# 1. 임베딩 레이어: 텍스트 데이터를 임베딩 벡터로 변환합니다.
embedding_layer = Embedding(input_dim=len(word_index) + 1, output_dim=embedding_dim)
embedded_sequences = embedding_layer(inputs)
# 2. 포지셔널 인코딩 추가
embedded_sequences_with_positional_encoding = embedded_sequences + positional_encoding
# 3. 멀티헤드 어텐션 레이어 추가
attention_layer = MultiHeadSelfAttentionLayer(num_heads=8, key_dim=embedding_dim)
attention_output = attention_layer(embedded_sequences_with_positional_encoding)
# 4. 잔차 연결
attention_output_with_residual = Add()([embedded_sequences_with_positional_encoding, attention_output])
# 5. GlobalAveragePooling1D 레이어 추가 (시퀀스의 전체 정보를 요약하여 다음 레이어로 전달)
pooled_output = GlobalAveragePooling1D()(attention_output_with_residual)
# 6. 피드 포워드 신경망
dense_layer = Dense(128, activation='relu')(pooled_output)
dropout_layer = Dropout(0.5)(dense_layer)
output_layer = Dense(1, activation='sigmoid')(dropout_layer)
# 모델 생성
model = Model(inputs=inputs, outputs=output_layer)
# 모델 컴파일
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 모델 학습
history = model.fit(X_train, np.array(y_train), epochs=10, batch_size=16, validation_data=(X_val, np.array(y_val)))
# 샘플 데이터 예측
sample_texts = ["I absolutely love this!", "I can't stand this product"]
sample_sequences = tokenizer.texts_to_sequences(sample_texts)
sample_data = tf.keras.preprocessing.sequence.pad_sequences(sample_sequences, maxlen=max_len, padding='post')
predictions = model.predict(sample_data)
for i, text in enumerate(sample_texts):
print(f"Text: {text}")
print(f"Prediction: {'Positive' if predictions[i] > 0.5 else 'Negative'}")
# 샘플 데이터 예측
sample_texts = ["This is the best thing I’ve ever bought!", "I’m extremely happy with the results.", "The service was terrible and rude.", "I regret buying this, it’s a complete waste of money."]
sample_sequences = tokenizer.texts_to_sequences(sample_texts)
sample_data = tf.keras.preprocessing.sequence.pad_sequences(sample_sequences, maxlen=max_len, padding='post')
predictions = model.predict(sample_data)
for i, text in enumerate(sample_texts):
print(f"Text: {text}")
print(f"Prediction: {'Positive' if predictions[i] > 0.5 else 'Negative'}")
6. 마스크드 어텐션
트랜스포머에서 마스크드 어텐션(Masked Attention) 은 디코더에서 사용되는 기법으로, 한 단어를 예측할 때 아직 생성되지 않은 미래 단어를 보지 못하도록 막는 역할을 합니다. 일반적인 셀프 어텐션은 문장 내 모든 단어를 동시에 참조할 수 있지만, 텍스트 생성 과정에서는 모델이 미래 단어까지 미리 보는 것은 부정확한 학습으로 이어집니다. 이를 방지하기 위해 어텐션 스코어 행렬의 미래 위치에 를 채워 소프트맥스 결과가 0이 되도록 처리합니다. 이렇게 하면 디코더는 오직 현재 단어 이전의 정보만 활용하여 다음 단어를 예측할 수 있고, 자연스러운 순차적 텍스트 생성을 가능하게 합니다.

'인공지능 > 자연어 처리' 카테고리의 다른 글
| Language Model 발전 (0) | 2025.09.08 |
|---|---|
| 사전 학습된 언어 모델(PML) (0) | 2025.09.08 |
| 어텐션 메커니즘 (7) | 2025.08.28 |
| Seq2Seq (1) | 2025.08.28 |
| LSTM과 GRU (2) | 2025.08.27 |