
📓Google Colab에서 실습하기
이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.
학습 내용
텍스트 분류
학습 목표
이 레슨을 완료하면:
- •텍스트를 신경망에 입력하기 위한 전처리 과정을 설명합니다
- •토큰화, 어휘 사전, 임베딩의 개념과 관계를 이해합니다
- •LSTM 기반 감성 분석 모델을 구현합니다
- •RNN과 LSTM의 차이점을 설명할 수 있습니다
텍스트를 숫자로 바꾸는 과정
비유: 신경망은 오직 숫자만 이해합니다. 텍스트를 신경망에 넣는 것은, 한국어를 모르는 외국인에게 한국 소설을 읽히는 것과 같습니다. 먼저 외국인이 이해할 수 있는 형태(숫자)로 "번역"해야 합니다.
텍스트 전처리는 세 단계를 거칩니다:
| 단계 | 입력 | 출력 | 설명 |
|---|---|---|---|
| 1. 토큰화 | 문장 | 단어 목록 | 문장을 단어로 쪼갬 |
| 2. 인덱싱 | 단어 목록 | 숫자 목록 | 각 단어에 번호 부여 |
| 3. 임베딩 | 숫자 목록 | 벡터 목록 | 번호를 의미 벡터로 변환 |
전체 과정 예시
python# 원본 텍스트 "This movie is really great!" # 1단계: 토큰화 -> 단어로 분리 ["this", "movie", "is", "really", "great", "!"] # 2단계: 인덱싱 -> 숫자로 변환 [42, 156, 11, 89, 2341, 5] # 3단계: 임베딩 -> 의미 벡터로 변환 (각 숫자가 128차원 벡터로) [[0.2, -0.1, 0.5, ...], # "this"의 의미 벡터 [0.8, 0.3, -0.2, ...], # "movie"의 의미 벡터 [0.1, 0.0, 0.3, ...], # "is"의 의미 벡터 ...]
1단계: 토큰화 (Tokenization)
비유: 토큰화는 문장을 레고 블록처럼 작은 조각으로 분해하는 것입니다. 어떤 크기로 분해하느냐에 따라 결과가 달라집니다.
pythonfrom torchtext.data.utils import get_tokenizer # 영어 기본 토크나이저 tokenizer = get_tokenizer("basic_english") tokens = tokenizer("This movie is great!") # 결과: ["this", "movie", "is", "great", "!"]
토큰화 방식 비교:
| 방식 | 예시 입력 | 결과 | 특징 |
|---|---|---|---|
| 단어 단위 | "I love cats" | ["I", "love", "cats"] | 직관적, 어휘 크기 큼 |
| 서브워드 | "playing" | ["play", "##ing"] | 어휘 크기 적당 |
| 문자 단위 | "cat" | ["c", "a", "t"] | 어휘 매우 작음, 시퀀스 길어짐 |
현대 모델(BERT, GPT)은 대부분 서브워드 토큰화를 사용합니다.
2단계: 어휘 사전 (Vocabulary)
비유: 어휘 사전은 "단어-번호 변환표"입니다. 도서관에서 각 책에 고유 번호를 붙이듯, 각 단어에 고유 번호를 부여합니다.
python# 간단한 어휘 사전 구축 예시 vocab = { "<pad>": 0, # 패딩 (빈 칸 채우기용) "<unk>": 1, # 미등록 단어 (사전에 없는 단어) "this": 2, "movie": 3, "is": 4, "great": 5, "terrible": 6, # ... 수천~수만 단어 } # 문장을 숫자로 변환 def encode(tokens, vocab): return [vocab.get(token, vocab["<unk>"]) for token in tokens] encoded = encode(["this", "movie", "is", "great"], vocab) # 결과: [2, 3, 4, 5]
왜 pad와 unk가 필요한가?
| 특수 토큰 | 역할 | 예시 |
|---|---|---|
| pad | 길이가 다른 문장을 같은 길이로 맞춤 | "hi" -> [24, 0, 0, 0, 0] |
| unk | 학습 시 본 적 없는 단어 처리 | "supercalifragilistic" -> 1 |
3단계: 임베딩 (Embedding)
비유: 임베딩은 단어의 "주민등록번호"를 "성격 프로필"로 바꾸는 과정입니다. 숫자 42는 아무런 의미 정보를 담고 있지 않지만, [0.2, -0.1, 0.5, ...]같은 벡터는 그 단어의 의미를 담고 있습니다.
python⚠️ 로컬 실행 필요# 임베딩 레이어: 숫자 인덱스 -> 의미 벡터 embedding = nn.Embedding( num_embeddings=10000, # 어휘 크기 (단어 수) embedding_dim=128 # 벡터 차원 (의미 공간의 차원 수) ) # 사용 예시 import torch word_indices = torch.tensor([2, 3, 4, 5]) # "this movie is great" word_vectors = embedding(word_indices) # 결과 크기: [4, 128] - 4개 단어, 각각 128차원 벡터
임베딩의 핵심 특성:
- •비슷한 의미의 단어는 비슷한 벡터를 가지게 됩니다
- •예: "good"과 "great"의 벡터가 가까움
- •이 벡터 값들은 학습 과정에서 자동으로 조정됩니다
LSTM 이해하기
RNN의 문제점
비유: 기본 RNN은 "금붕어 기억력"을 가지고 있습니다. 긴 문장의 앞부분을 읽을 때쯤이면 뒷부분은 까먹습니다. 이것이 "기울기 소실(vanishing gradient)" 문제입니다.
LSTM의 해결책
비유: LSTM은 "수첩을 가진 학생"입니다. 중요한 정보는 수첩(셀 상태)에 적어두고, 필요 없는 정보는 지우며, 필요할 때 꺼내 봅니다.
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| 셀 상태 (Cell State) | 장기 기억 저장소 | 수첩 |
| 망각 게이트 (Forget Gate) | 불필요한 정보 삭제 | 수첩에서 줄 긋기 |
| 입력 게이트 (Input Gate) | 새 정보 저장 결정 | 수첩에 새로 적기 |
| 출력 게이트 (Output Gate) | 현재 필요한 정보 출력 | 수첩에서 읽기 |
python# LSTM 레이어 생성 lstm = nn.LSTM( input_size=128, # 입력 벡터 크기 (= 임베딩 차원) hidden_size=256, # 은닉 상태 크기 batch_first=True, # 입력 형태: [배치, 시퀀스길이, 특성] num_layers=1 # LSTM 층 수 )
감성 분석 모델 구현
감성 분석(Sentiment Analysis)은 텍스트가 긍정적인지 부정적인지 판단하는 작업입니다.
pythonclass SentimentClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes): super().__init__() # 1. 임베딩: 단어 인덱스 -> 의미 벡터 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) # 2. LSTM: 순서 정보를 활용한 문맥 파악 self.lstm = nn.LSTM( embed_dim, # 입력 크기 = 임베딩 차원 hidden_dim, # 은닉 상태 크기 batch_first=True, bidirectional=False # 단방향 (앞에서 뒤로만 읽음) ) # 3. 분류기: 최종 은닉 상태 -> 클래스 예측 self.fc = nn.Linear(hidden_dim, num_classes) self.dropout = nn.Dropout(0.3) def forward(self, x): # x: [배치크기, 시퀀스길이] - 숫자로 인코딩된 문장 embedded = self.embedding(x) # embedded: [배치크기, 시퀀스길이, embed_dim] # LSTM에 임베딩 벡터를 순서대로 입력 output, (hidden, cell) = self.lstm(embedded) # hidden: [1, 배치크기, hidden_dim] - 마지막 은닉 상태 # 마지막 은닉 상태로 감성 분류 hidden = self.dropout(hidden.squeeze(0)) logits = self.fc(hidden) # logits: [배치크기, num_classes] return logits # 모델 생성 model = SentimentClassifier( vocab_size=10000, # 어휘 크기 embed_dim=128, # 임베딩 차원 hidden_dim=256, # LSTM 은닉 크기 num_classes=2 # 긍정(1) / 부정(0) )
왜 마지막 은닉 상태만 사용하는가?
LSTM의 마지막 은닉 상태(hidden)는 전체 문장을 읽은 후의 "요약"입니다. 문장 전체의 맥락을 압축한 벡터이므로, 감성 분류에 적합합니다.
학습 코드
pythoncriterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) for epoch in range(10): model.train() total_loss = 0 for texts, labels in train_loader: texts, labels = texts.to(device), labels.to(device) optimizer.zero_grad() output = model(texts) loss = criterion(output, labels) loss.backward() # 그래디언트 클리핑: RNN 계열에서 기울기 폭발 방지 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() total_loss += loss.item() avg_loss = total_loss / len(train_loader) print("Epoch {}, Loss: {:.4f}".format(epoch+1, avg_loss))
왜 그래디언트 클리핑이 필요한가?
RNN/LSTM은 시퀀스가 길어지면 역전파 시 그래디언트가 폭발적으로 커질 수 있습니다. clip_grad_norm_은 그래디언트의 크기를 max_norm 이하로 제한하여 안정적인 학습을 보장합니다.
이미지 분류 vs 텍스트 분류 비교
| 항목 | 이미지 분류 (CNN) | 텍스트 분류 (LSTM) |
|---|---|---|
| 입력 형태 | 2D 픽셀 격자 | 1D 단어 시퀀스 |
| 전처리 | 정규화, 크기 조정 | 토큰화, 임베딩 |
| 핵심 레이어 | Conv2d | LSTM/GRU |
| 패턴 유형 | 공간적 패턴 | 순차적 패턴 |
| 주요 과제 | 위치/크기 변화 | 문맥 이해 |
핵심 요약
| 개념 | 설명 | 비유 |
|---|---|---|
| 토큰화 | 문장을 단어로 분리 | 문장을 레고 블록으로 분해 |
| 어휘 사전 | 단어에 고유 번호 부여 | 도서관 분류 번호 |
| 임베딩 | 번호를 의미 벡터로 변환 | 번호를 성격 프로필로 변환 |
| LSTM | 순서 정보로 문맥 파악 | 수첩 들고 읽는 학생 |
| 감성 분석 | 텍스트의 감정 판단 | 영화 리뷰 평가 |
학습 체크리스트
- • 텍스트 전처리 3단계(토큰화 -> 인덱싱 -> 임베딩)를 설명할 수 있다
- • pad 토큰과 unk 토큰의 역할을 안다
- • 임베딩 레이어가 하는 일을 설명할 수 있다
- • LSTM이 RNN의 어떤 문제를 해결하는지 안다
- • 그래디언트 클리핑이 필요한 이유를 설명할 수 있다
- • 감성 분석 모델의 전체 구조를 이해한다
레슨 정보
- 레벨
- Level 4: 실전 프로젝트
- 예상 소요 시간
- 약 4분
- 참고 영상
- YouTube 링크
💡실습 환경 안내
이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.
Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.