Text Classification과 Sentiment Analysis
본인 사본을 만들어 실행·수정·기록하세요.
- 이름: ⟦여기에 직접 입력⟧
- 학번: ⟦여기에 직접 입력⟧
- Code Cell 실행: ▶ 또는
Shift+Enter, 위에서부터 순서대로. - Text Cell 작성: 더블클릭 →
직접 입력작성 →Shift+Enter. - 직접 작성 셀: 본인 코드 입력.
- 값 변경 후: 해당 셀과 뒤의 관련 셀 다시 실행.
1. 토큰과 vocabulary
문장을 Token과 Index로 바꾸고, Embedding 벡터를 평균 내어 문장 전체의 감정을 분류합니다. 이번 실습은 전처리 결과와 문장 벡터를 직접 확인하기 위해 짧은 영어 Review와 학습 가능한 Mean Embedding 모델을 사용합니다. 강의의 IMDB 예제처럼 문장 전체에 Label을 붙이되, 여기서는 세 감정으로 분류합니다. Mean Pooling은 단어 순서를 생략한 단순한 모델이며 RNN을 반드시 대체해야 해서 사용하는 것은 아닙니다. TorchText 설치, Hugging Face 로그인, 외부 모델 다운로드는 필요하지 않습니다. PAD=0은 빈자리, UNK=1은 모르는 단어입니다. screen size okay처럼 짧은 문장에 PAD가 붙는지 확인하고, 아래 비교 셀에서도 다른 길이의 문장을 사용합니다.
A. 기준 예제 · 먼저 읽고 실행
import sys, numpy as np, matplotlib.pyplot as plt
import torch
from torch import nn
import torch.nn.functional as F
torch.manual_seed(42)
torch.set_num_threads(2)
print('Python:', sys.version.split()[0], '| PyTorch:', torch.__version__)
texts=['great battery bright screen','bad camera slow app','screen size okay','love fast performance','broken charger bad battery','average design normal speed','great sound love display','slow response hate keyboard','okay price average camera','bright screen fast app','bad screen okay battery','normal package average manual']
y=torch.tensor([2,0,1,2,0,1,2,0,1,2,0,1]);names=['negative','neutral','positive']
words=sorted(set(' '.join(texts).split()));vocab={'<PAD>':0,'<UNK>':1,**{w:i+2 for i,w in enumerate(words)}}
def encode(text_list):
rows=[[vocab.get(w,1) for w in text.lower().split()] or [1] for text in text_list]
length=max(map(len,rows));return torch.tensor([r+[0]*(length-len(r)) for r in rows])
X=encode(texts);print(texts[:3],X[:3],X.shape)
# 길이가 다를 때 PAD가 들어가는 위치를 확인합니다.
probe_texts = ['great battery', 'great battery bright screen', 'excellent battery']
probe_ids = encode(probe_texts)
for text, row in zip(probe_texts, probe_ids):
print('text:', text, '| tokens:', text.lower().split(), '| ids:', row.tolist())
assert (probe_ids[0] == 0).sum().item() == 2
assert probe_ids[2, 0].item() == vocab['<UNK>']
B. 직접 수행
- 길이가 다른 두 영어 문장을 Token → Index → PAD 순서로 출력하세요.
- 학습 사전에 없는 단어 하나를 넣고 UNK 위치를 확인하세요.
- 실제 단어를 뜻하는 Index와 PAD·UNK를 구별해 적으세요.
예상은 정확한 수치 대신 shape나 증가·감소 방향으로 적어도 됩니다. 설명은 아래 Text Cell에 기록하세요.
# ✍ 직접 작성: 이 셀 아래에 본인의 코드를 추가하세요.
# 코드가 길어지면 Code Cell을 추가해도 됩니다.
C. 직접 기록 · 이 Text Cell을 더블클릭해서 수정
- 확인하거나 바꾼 조건: ⟦직접 입력⟧
- 실행 전 예상: ⟦직접 입력⟧
- 실제 출력/그래프에서 확인한 값: ⟦직접 입력⟧
- 예상과 차이 및 설명: ⟦직접 입력⟧
2. 마스킹한 평균과 학습
12·13주차는 문자마다 다음 문자를 예측했지만 이번에는 문장당 감정 하나를 예측합니다. Embedding (N,S,16)에서 PAD를 뺀 평균 (N,16)을 만든 뒤 Linear로 (N,3) Logit을 만듭니다. Embedding과 Linear는 함께 학습되며, 이 모델에는 RNN/LSTM이 없습니다.
A. 기준 예제 · 먼저 읽고 실행
class TextClassifier(nn.Module):
def __init__(self):
super().__init__();self.embedding=nn.Embedding(len(vocab),16,padding_idx=0);self.fc=nn.Linear(16,3)
def forward(self,ids):
mask=(ids!=0).unsqueeze(-1);embedded=self.embedding(ids)
pooled=(embedded*mask).sum(1)/mask.sum(1).clamp_min(1)
return self.fc(pooled)
model=TextClassifier();opt=torch.optim.Adam(model.parameters(),lr=.02);losses=[];accuracies=[]
for epoch in range(200):
logits=model(X);loss=F.cross_entropy(logits,y)
opt.zero_grad();loss.backward();opt.step()
losses.append(loss.item());accuracies.append((logits.argmax(1)==y).float().mean().item())
print('Training accuracy:',accuracies[-1]);print('embedding:',model.embedding(X).shape)
fig,axes=plt.subplots(1,2,figsize=(9,3));axes[0].plot(losses);axes[1].plot(accuracies)
for ax,yl in zip(axes,['Loss','Accuracy']):ax.set(xlabel='Epoch',ylabel=yl);ax.grid()
plt.show()
# 같은 문장을 혼자 처리하거나 긴 문장과 묶어도, 올바른 평균은 같습니다.
with torch.no_grad():
single_ids = encode(['great battery'])
batch_ids = encode(['great battery', 'great battery bright screen'])
single_vector = model.embedding(single_ids).mean(1)[0]
batch_embedded = model.embedding(batch_ids)
mask = (batch_ids != 0).unsqueeze(-1)
masked_vectors = (batch_embedded * mask).sum(1) / mask.sum(1).clamp_min(1)
plain_vectors = batch_embedded.mean(1)
print('PAD 포함 평균과 차이:', (single_vector - plain_vectors[0]).abs().max().item())
print('PAD 제외 평균과 차이:', (single_vector - masked_vectors[0]).abs().max().item())
assert torch.allclose(single_vector, masked_vectors[0], atol=1e-6)
order_logits = model(encode(['great battery', 'battery great']))
print('단어 순서를 바꾼 두 문장의 Logit:', order_logits)
assert torch.allclose(order_logits[0], order_logits[1], atol=1e-6)
B. 직접 수행
- 제공된 두 문장의 평균 비교 셀을 실행하세요. 짧은 문장을 단독 처리한 Vector를 기준으로, 긴 문장과 함께 처리한 Vector의 최대 절대 차이를 Mask 적용 전·후 각각 기록하세요.
- 비교 셀에서
single_ids의 문장과batch_ids의 첫 문장을 같은 짧은 문장으로 함께 바꾸세요.batch_ids의 둘째 문장은 그보다 긴 문장으로 바꾸고 같은 계산을 반복하세요. 사용한 문장과 두 차이를 기록하세요. 학습한 Embedding 가중치는 유지합니다. - 단어 순서를 바꾼 두 문장의 Logit을 나란히 적고 최대 절대 차이를 구하세요. 현재 문장의 Token 수·PAD 수와 출력된 최대 차이를 근거로, PAD 포함 여부가 평균에 미친 영향과 평균 Pooling에서 단어 순서가 바뀐 영향을 나누어 설명하세요.
예상은 정확한 수치 대신 shape나 증가·감소 방향으로 적어도 됩니다. 설명은 아래 Text Cell에 기록하세요.
# ✍ 직접 작성: 이 셀 아래에 본인의 코드를 추가하세요.
# 코드가 길어지면 Code Cell을 추가해도 됩니다.
C. 직접 기록 · 이 Text Cell을 더블클릭해서 수정
- 확인하거나 바꾼 조건: ⟦직접 입력⟧
- 실행 전 예상: ⟦직접 입력⟧
- 실제 출력/그래프에서 확인한 값: ⟦직접 입력⟧
- 예상과 차이 및 설명: ⟦직접 입력⟧
3. 새 문장 직접 입력
아래 my_reviews의 빈 문자열 세 개를 영어 문장으로 바꾸세요. 처음에는 texts에 나온 단어를 사용하고, 다음에는 모르는 단어 하나를 추가합니다. 의도한 감정과 모델 예측이 다르더라도 결과와 이유를 기록하세요. 미작성 상태에서는 예측을 건너뜁니다.
A. 기준 예제 · 먼저 읽고 실행
my_reviews=['','',''] # 직접 입력: 긍정 1개, 중립 1개, 부정 1개
if all(s.strip() for s in my_reviews):
model.eval()
with torch.no_grad():prob=model(encode(my_reviews)).softmax(1)
for text,p in zip(my_reviews,prob):print(text,'=>',names[p.argmax().item()],p.tolist())
else:
print('my_reviews의 빈 문자열 3개를 직접 작성하고 이 셀을 다시 실행하세요.')
print('unknown token example:',encode(['excellent battery']))
B. 직접 수행
- 학습 사전의 단어로 긍정·중립·부정 영어 문장을 하나씩 my_reviews에 작성하세요.
- 의도한 감정·예측·Token을 기록하고, 모르는 단어나 문장부호를 하나 넣어 UNK 처리를 확인하세요.
- 예측이 의도와 달라도 원인을 설명하면 됩니다.
예상은 정확한 수치 대신 shape나 증가·감소 방향으로 적어도 됩니다. 설명은 아래 Text Cell에 기록하세요.
# ✍ 직접 작성: 이 셀 아래에 본인의 코드를 추가하세요.
# 코드가 길어지면 Code Cell을 추가해도 됩니다.
C. 직접 기록 · 이 Text Cell을 더블클릭해서 수정
- 확인하거나 바꾼 조건: ⟦직접 입력⟧
- 실행 전 예상: ⟦직접 입력⟧
- 실제 출력/그래프에서 확인한 값: ⟦직접 입력⟧
- 예상과 차이 및 설명: ⟦직접 입력⟧
반복 숙달 · 실행 전에 판단하기
앞의 직접 수행에서 비교한 조건 세 가지를 이 표에 정리하세요. 이미 수행한 실험을 다시 추가할 필요는 없습니다. 비교가 부족하면 한 조건만 바꾸어 보충하세요.
| 변경 조건 | 예상 shape/수치/결과 | 실제 결과 | 오류가 있었다면 원인 |
|---|---|---|---|
| ⟦직접 입력 1⟧ | ⟦직접 입력⟧ | ⟦직접 입력⟧ | ⟦직접 입력⟧ |
| ⟦직접 입력 2⟧ | ⟦직접 입력⟧ | ⟦직접 입력⟧ | ⟦직접 입력⟧ |
| ⟦직접 입력 3⟧ | ⟦직접 입력⟧ | ⟦직접 입력⟧ | ⟦직접 입력⟧ |
# ✍ 반복 실험 코드
선택 실습 · 오류의 원인을 찾아 코드 고치기
Embedding에 Float Tensor의 Token Index를 전달한 경우
문장 분류 모델 학습까지 마친 뒤, 노트북 맨 아래에 Code Cell을 추가하고 다음 코드를 실행하세요.
for debug_input in [X.float(), X]: print("Token Index:", debug_input.shape, debug_input.dtype) try: with torch.no_grad(): print("Output:", model(debug_input).shape) except (RuntimeError, ValueError) as error: print(type(error).__name__, str(error))Text Cell에 각 입력의 dtype과 실행 성공 여부를 표로 적으세요. Embedding 입력이 단어의 연속적인 측정값이 아니라 Vocabulary의 행 번호라는 점과 오류 원인을 연결해 설명하세요.
X.float()호출 후에도 원래X는 정수형입니다.
저장 전 확인
- 작성란과 출력·그래프를 확인하고 새 런타임에서 전체 셀을 순서대로 실행하세요.
- 본인 Drive에 저장한 뒤
.ipynb로 내려받으세요.
코드, 실제 Output·그래프, 직접 작성한 해석을 포함하세요. 제출은 KLAS를 이용하며 기한은 학기 일정과 최신 공지를 확인합니다.
파일명: 이름_학번_14주차.ipynb.