4.5 LSTM으로 영화 리뷰 분류하기 코드 리뷰

yunju·7일 전

코드 리뷰

목록 보기
5/5

노트북: 13-02. lstm_naver_classification.ipynb (코드 셀 73개)
예상 시간: 약 25분 · 짝꿍 글: 4.5_LSTM으로_네이버_영화_리뷰_분류하기.md
⏱ 공지대로 세션 시작 직후 학습까지 돌려 두고 설명을 시작한다.


개념 1분 — 이것만 알고 코드 보기

이 노트북은 4.1~4.4를 전부 이어 붙인 것이다. 리뷰 한 줄이 이렇게 흘러간다.

"아 더빙.. 진짜 짜증나네요 목소리"           ← 원문
 → "아 더빙 진짜 짜증나네요 목소리"          ← 정제: 한글과 공백만 남김         (셀 19)
 → ['아','더','빙','진짜','짜증','나','네요','목소리']  ← 토큰화 (4.3)          (셀 26)
 → [342, 1050, 787, 13, 199, 8, 64, 627]   ← 정수 인코딩: 단어 집합 번호      (셀 42)
 → [342, 1050, ..., 627, 0, 0, ..., 0]      ← 패딩: 길이 30으로 맞춤           (셀 50)
     shape (배치 32, 30)
 → nn.Embedding  → (32, 30, 100)            ← 임베딩 (4.4)
 → nn.LSTM       → hidden (1, 32, 128)      ← LSTM (4.2), 마지막 은닉 상태만
 → nn.Linear     → (32, 2)                  ← 부정/긍정 점수 → CrossEntropyLoss

(정수 값은 예시다.) 이 그림만 머리에 있으면 73개 셀이 전부 이 중 어딘가에 속한다.


발표 전 체크 — ⚠️ 이번 노트북이 가장 위험하다

1. 시작하자마자 "모두 실행"을 누르자. 학습 셀(64)은 앞의 전처리 셀이 전부 실행돼야 돈다. 런타임을 GPU로 바꾸고 바로 시작한다. 직접 돌려 보니 토큰화 약 25초, CPU 학습은 에폭당 1분 안팎이었다. 대신 검증·테스트가 느리다(아래 5번).

2. Mecab 설치(셀 0)가 막히면 대안이 있다. 셀 0은 셸 스크립트로 Mecab을 설치하는데, 환경에 따라 오래 걸리거나 실패할 수 있다. 막히면 셀 0과 셀 1의 Mecab 부분을 이렇게 바꾼다.

!pip install python-mecab-ko
from mecab import MeCab
mecab = MeCab()          # 셀 25의 mecab = Mecab() 대신

직접 확인한 결과는 이렇다.

  • 셀 25의 예문과 셀 27의 토큰화 결과가 노트북과 완전히 같다.
  • 단어 수는 45,216개로 노트북의 45,296개와 거의 같다. 단어 집합 크기도 19,188 vs 19,193이다.
  • 2 에폭 학습 후 검증 정확도는 84.9%로, 노트북의 84.8%와 같은 수준이다.

3. 셀 23: 테스트 데이터 정제가 조용히 건너뛰어질 수 있다. 셀 23의 str.replace에는 regex=True가 없다. 노트북을 만들 때의 pandas에서는 기본값이 정규식이었다. 저장된 출력에 FutureWarning: The default value of regex will change가 찍혀 있는 게 그 흔적이다. pandas 2.0부터는 기본값이 일반 문자열이라서, [^ㄱ-ㅎㅏ-ㅣ가-힣 ]라는 글자 그대로를 찾다가 아무것도 못 바꾼다. 실제 데이터로 확인했다.

남은 테스트 샘플 수문장부호
노트북 그대로 (최신 pandas)49,157남아 있음 (뭐야 이 평점들은....)
regex=True 추가48,852 (노트북 출력과 같음)제거됨

→ 셀 23의 두 str.replace에 , regex=True를 붙이자.

4. 셀 20: pandas 3에서는 빈 리뷰가 안 걸러진다. train_data['document'].replace('', np.nan, inplace=True)처럼 열을 꺼낸 뒤 inplace로 바꾸는 코드는 pandas 3에서 원본에 반영되지 않는다(경고만 뜬다). 직접 돌려 보니 걸러져야 할 빈 리뷰 789개가 그대로 남았다. 코랩의 pandas가 2.x면 경고와 함께 동작하지만, 안전하게 고쳐 두자.

train_data['document'] = train_data['document'].replace('', np.nan)   # inplace 대신 대입

셀 23의 test_data['document'].replace('', np.nan, inplace=True)도 같다. 코랩 버전은 pd.__version__으로 확인한다.

5. 검증·테스트 로더의 batch_size=1(셀 57)이 느리다. 검증 29,079개와 테스트 48,852개를 한 개씩 모델에 넣는다. 에폭마다 검증이 학습보다 오래 걸릴 수 있다. 시간이 빠듯하면 valid_dataloader의 batch_size를 512 정도로 바꾸자. 정확도 계산 방식은 배치 크기와 상관없게 짜여 있어서 결과가 같다. 손실 평균만 아주 조금 달라진다.

6. 사소한 것.

  • 셀 59의 learning_rate = 0.01은 쓰이지 않는다. 셀 61이 lr=0.001을 직접 쓴다.
  • num_epochs가 셀 58(5)과 셀 59(10)에서 두 번 정해진다. 실제로는 10이 쓰인다.

흐름 한눈에

파트셀한 줄 메시지시간
1. 설치·데이터 로드0~7리뷰 15만 + 5만, 라벨 0/11분
2. 정제8~23중복·결측 제거, 한글만 남기기3분
3. 토큰화·불용어24~28Mecab으로 형태소 → 조사 빼기 (4.3)2분
4. 데이터 나누기29~31훈련 8 : 검증 2, 비율 유지1분
5. 단어 집합32~40빈도순 번호, 희귀 단어 버리기3분
6. 정수 인코딩41~45단어 → 번호, 모르면 <UNK>2분
7. 패딩46~51길이 30으로 자르고 0으로 채움2분
8. 텐서·데이터로더52~582.3의 DataLoader 그대로1분
9. 모델56, 59~60Embedding → LSTM → Linear (4.4 + 4.2)4분
10. 학습 루프61~642장부터 쓰던 루프 + 체크포인트3분
11. 평가·예측65~72테스트 84.7%, 새 문장 예측2분
토론—뒤 패딩 + 마지막 은닉 상태시간 남으면

읽는 법: 🗣 = 그대로 말해도 되는 문장, ❓ = 나올 만한 질문과 답


Part 1. 설치·데이터 로드 (셀 0~7)

urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt", ...)
train_data = pd.read_table('ratings_train.txt')     # 탭으로 구분된 파일
test_data  = pd.read_table('ratings_test.txt')
  • NSMC(네이버 영화 리뷰 데이터). 훈련 150,000개, 테스트 50,000개다.
  • 열은 세 개다. id, document(리뷰 본문), label(0 = 부정, 1 = 긍정).
  • 셀 1의 임포트 중 pickle은 쓰이지 않는다.

Part 2. 정제 (셀 8~23)

중복과 결측 (셀 8~17)

train_data['document'].nunique()                          # 146,182 — 리뷰가 15만인데 고유값은 14만 6천
train_data.drop_duplicates(subset=['document'], inplace=True)   # 같은 리뷰 제거 → 146,183
train_data = train_data.dropna(how='any')                 # 본문이 비어 있는(NaN) 1개 제거 → 146,182
  • 같은 리뷰가 여러 번 있으면 모델이 그 문장을 외우기 쉽다. 그래서 지운다.
  • 셀 12: 부정 73,342 / 긍정 72,841로 거의 반반이다. 정확도만 봐도 괜찮은 데이터다.

한글만 남기기 (셀 18~22)

train_data['document'] = train_data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]", "", regex=True)
  • 정규식 [^...] = "대괄호 안에 없는 것". ㄱ-ㅎ(자음), ㅏ-ㅣ(모음), 가-힣(완성형 한글), 공백만 남기고 다 지운다. 영어, 숫자, 문장부호, 이모티콘이 빠진다.
  • 셀 18은 같은 원리를 영어([^a-zA-Z ])로 먼저 보여준 것이다.
  • 셀 20: 정제 후 빈 문자열이 된 리뷰(원래 ㅋㅋ!!나 10점 같은 것)를 NaN으로 바꾼다. 셀 22에서 지운다 → 145,393개. (⚠️ 발표 전 체크 4번)
  • 셀 23: 테스트 데이터도 똑같이 정제한다 → 48,852개. (⚠️ 발표 전 체크 3번)

🗣 "테스트 데이터도 훈련 데이터와 똑같이 처리해야 해요. 훈련 때 문장부호를 다 지웠는데 테스트에 남아 있으면, 모델이 처음 보는 토큰이 잔뜩 들어가게 됩니다. 그래서 셀 23에 regex=True가 빠진 게 생각보다 중요해요."


Part 3. 토큰화·불용어 (셀 24~28)

stopwords = ['도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', ...]
mecab = Mecab()

X_train = []
for sentence in tqdm(train_data['document']):
    tokenized_sentence = mecab.morphs(sentence)                    # 형태소 토큰화 (4.3)
    stopwords_removed_sentence = [w for w in tokenized_sentence if not w in stopwords]   # 불용어 제거
    X_train.append(stopwords_removed_sentence)
  • mecab.morphs() → 4.3의 okt.morphs()와 같은 역할이다. 리뷰 14만 5천 개가 약 10초 걸린다.
  • 불용어(stopwords) = 자주 나오지만 감성 판단에 도움이 안 되는 토큰이다. 주로 조사와 어미다. 직접 정한 목록이다.
  • 결과는 리스트의 리스트다. 리뷰 하나 = 토큰 리스트 하나.
  • 셀 27 출력의 세 번째 리뷰: ['너무', '재', '밓었다그래서보는것을추천한다']. 원문의 오타(재밓었다)와 띄어쓰기 없음 때문에 형태소 분석이 실패해서, 뒷부분이 통째로 한 토큰이 됐다. 이런 토큰은 거의 한 번만 나와서 5부에서 버려진다. 진짜 데이터는 지저분하다.

Part 4. 데이터 나누기 (셀 29~31)

y_train = np.array(train_data['label'])
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.2, random_state=0, stratify=y_train)
  • 훈련 데이터의 20%를 검증용으로 뗀다. 테스트는 마지막에 한 번만 본다(3.5의 과적합 이야기와 연결).
  • stratify=y_train → 나눈 뒤에도 부정/긍정 비율이 같게 유지한다. 셀 31에서 훈련·검증 모두 부정 50.24%로 같다.
  • random_state=0 → 매번 같은 방식으로 나눈다(재현성).

Part 5. 단어 집합 만들기 (셀 32~40)

word_counts = Counter(word_list)                                  # 단어별 등장 횟수 → 45,296종
vocab = sorted(word_counts, key=word_counts.get, reverse=True)    # 많이 나온 순서로 정렬
# 셀 35: 등장 2번 이하인 희귀 단어 26,105개 → 단어 종류의 57.6%, 전체 등장의 2.3%
vocab = vocab[:vocab_size]                                        # 상위 19,191개만 남김

word_to_index = {'<PAD>': 0, '<UNK>': 1}
for index, word in enumerate(vocab):
    word_to_index[word] = index + 2                               # 빈도 1위 '영화' → 2
  • 희귀 단어 버리기가 핵심이다. 셀 35의 두 숫자를 같이 보자.
    • 2번 이하로 나온 단어가 단어 종류로는 57.6% 다.
    • 그런데 실제 등장 횟수로는 2.3% 뿐이다.
    • 즉 단어 집합의 절반 이상이 거의 안 나오는 단어다. 버려도 정보 손실은 작고, 임베딩 테이블(4.4)은 절반 이하로 줄어든다.
  • 번호는 빈도순이다. 셀 40: 영화 → 2(1위).
  • <PAD>=0, <UNK>=1. 4.4 노트북과 번호가 반대라는 점에 주의하자.
  • 최종 단어 집합 크기 vocab_size = 19,191 + 2 = 19,193 → 임베딩 테이블의 행 수가 된다.

🗣 "단어 종류의 절반 넘게가 두 번 이하로만 나오는 단어예요. 그런데 다 합쳐도 전체 등장의 2%밖에 안 돼서, 과감하게 버리고 UNK로 처리합니다."


Part 6. 정수 인코딩 (셀 41~45)

def texts_to_sequences(tokenized_X_data, word_to_index):
  encoded_X_data = []
  for sent in tokenized_X_data:
    index_sequences = []
    for word in sent:
      try:
          index_sequences.append(word_to_index[word])      # 단어 → 번호
      except KeyError:
          index_sequences.append(word_to_index['<UNK>'])   # 없는 단어 → 1
    encoded_X_data.append(index_sequences)
  return encoded_X_data
  • 4.4 셀 4의 try/except와 완전히 같은 구조다.
  • 검증·테스트도 훈련 데이터로 만든 단어 집합으로 바꾼다. 테스트에만 있는 단어는 UNK가 된다.
  • 셀 44~45: 번호 → 단어 역사전(index_to_word)을 만들어 되돌려 본다. 원래 토큰과 같으면 인코딩이 맞게 된 것이다.

Part 7. 패딩 (셀 46~51)

# 셀 46: 최대 길이 74, 평균 12.3
max_len = 30                         # 셀 48: 길이 30 이하가 92.5%

def pad_sequences(sentences, max_len):
  features = np.zeros((len(sentences), max_len), dtype=int)       # 전부 0(<PAD>)으로 시작
  for index, sentence in enumerate(sentences):
    if len(sentence) != 0:
      features[index, :len(sentence)] = np.array(sentence)[:max_len]   # 앞에서부터 채움
  return features
  • RNN은 배치 안의 문장 길이가 같아야 (배치, 시점) 텐서로 묶인다. 그래서 길이를 30으로 맞춘다.
  • np.zeros(...) → 0(<PAD>)으로 채운 판을 만든다.
  • features[index, :len(sentence)] = ...[:max_len] →
    • 짧은 리뷰는 앞에 내용을 쓰고 뒤를 0으로 남긴다(뒤 패딩).
    • 30보다 긴 리뷰는 앞 30개만 남기고 뒤를 자른다.
  • 결과 shape: 훈련 (116314, 30), 검증 (29079, 30), 테스트 (48852, 30).
  • 셀 51: 첫 리뷰는 토큰 7개 + 0이 23개.
  • if len(sentence) != 0 → 토큰이 하나도 없는 리뷰는 전부 0인 줄이 된다.

❓ 왜 30? 30이면 92.5%의 리뷰가 잘리지 않는다. 74로 하면 하나도 안 잘리지만, 대부분이 PAD인 계산을 하게 된다. 길이와 손실 사이의 타협이다.


Part 8. 텐서와 데이터로더 (셀 52~58)

encoded_train = torch.tensor(padded_X_train).to(torch.int64)                       # 정수형 (임베딩 입력이니까)
train_dataset = torch.utils.data.TensorDataset(encoded_train, train_label_tensor)
train_dataloader = torch.utils.data.DataLoader(train_dataset, shuffle=True, batch_size=32)
  • 2.3에서 배운 TensorDataset + DataLoader 그대로다.
  • .to(torch.int64) → 임베딩 층의 입력은 정수(Long) 여야 한다(4.4).
  • 배치 하나 = (32, 30) 정수 + (32,) 라벨. 배치 수는 116,314 / 32 → 3,635(셀 58).
  • 검증·테스트 로더는 batch_size=1이다. (⚠️ 발표 전 체크 5번)

Part 9. 모델 (셀 56, 59~60) ★ 4.1~4.4가 만나는 곳

class TextClassifier(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim):
        super(TextClassifier, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)            # 4.4
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)    # 4.2
        self.fc = nn.Linear(hidden_dim, output_dim)                         # 2장

    def forward(self, x):
        embedded = self.embedding(x)
        lstm_out, (hidden, cell) = self.lstm(embedded)     # LSTM은 튜플로 받는다 (4.2)
        last_hidden = hidden.squeeze(0)
        logits = self.fc(last_hidden)
        return logits

하이퍼파라미터(셀 59): embedding_dim=100, hidden_dim=128, output_dim=2

shape 따라가기 (배치 32 기준)

단계코드shape근거
입력x(32, 30)패딩된 정수
임베딩self.embedding(x)(32, 30, 100)4.4: 정수 → 100차원 벡터
LSTM 모든 시점lstm_out(32, 30, 128)4.1: outputs. 쓰지 않는다
LSTM 마지막 은닉hidden(1, 32, 128)4.1: 맨 앞은 층 수
층 차원 제거hidden.squeeze(0)(32, 128)
출력self.fc(...)(32, 2)부정/긍정 점수(로짓)
  • 다 대 일(4.1의 입출력 구조)이다. 리뷰를 끝까지 읽은 마지막 은닉 상태 하나만 fc층에 넣는다.
  • squeeze(0) → 0번 축의 크기 1을 없앤다. (1, 32, 128) → (32, 128). 층이 하나라서 가능하다. 여러 층이면 hidden[-1]을 쓴다.
  • 출력이 2칸인 이유: CrossEntropyLoss(2.4 소프트맥스 회귀)로 2개 클래스를 분류한다. 시그모이드 1칸으로 해도 된다.

파라미터 수 (직접 셈)

층계산개수비율
Embedding19,193 × 1001,919,30094%
LSTM4 × (128×100 + 128×128 + 128 + 128)117,7606%
Linear128 × 2 + 22580.01%
합계2,037,318

🗣 "모델 코드는 딱 세 층이에요. 4.4의 임베딩이 정수를 벡터로 바꾸고, 4.2의 LSTM이 리뷰를 끝까지 읽고, 마지막 은닉 상태 하나를 Linear가 긍정·부정 점수로 바꿉니다. 파라미터는 94%가 임베딩 표예요."


Part 10. 학습 루프 (셀 61~64)

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(num_epochs):                      # 10
    model.train()
    for batch_X, batch_y in train_dataloader:
        batch_X, batch_y = batch_X.to(device), batch_y.to(device)
        logits = model(batch_X)                       # 순전파
        loss = criterion(logits, batch_y)             # 손실
        optimizer.zero_grad()                         # 기울기 초기화
        loss.backward()                               # 역전파
        optimizer.step()                              # 가중치 갱신
    val_loss, val_accuracy = evaluate(model, valid_dataloader, criterion, device)
    if val_loss < best_val_loss:                      # 검증 손실이 가장 낮을 때만
        torch.save(model.state_dict(), 'best_model_checkpoint.pth')   # 저장
  • 안쪽 다섯 줄은 2.1부터 써 온 학습 루프 그대로다. 모델만 바뀌었다.
  • calculate_accuracy → argmax로 큰 점수 쪽을 예측으로 보고, 맞은 비율을 센다.
  • evaluate → model.eval() + torch.no_grad(). 평가할 때는 기울기를 안 만든다.
  • 체크포인트: 에폭마다 검증 손실을 보고, 가장 낮을 때의 가중치만 파일로 남긴다.

저장된 학습 로그 (GPU)

에폭훈련 손실훈련 정확도검증 손실검증 정확도저장
10.47674.5%0.36783.4%✔
20.32086.1%0.34284.8%✔
30.25989.3%0.34685.1%
50.15594.2%0.41784.6%
100.05098.3%0.67984.0%
  • 2 에폭 이후 과적합이다. 훈련 정확도는 98%까지 오르는데 검증 손실은 0.34 → 0.68로 오히려 커진다(3.5에서 본 그래프 그대로).
  • 그래서 체크포인트는 2 에폭 것이 남는다. 3~10 에폭은 사실상 버리는 학습이다.

🗣 "학습 루프는 2장부터 쓰던 그 다섯 줄이에요. 로그를 보면 2 에폭부터 훈련 정확도만 오르고 검증 손실은 커지죠. 전형적인 과적합이라, 검증 손실이 제일 낮았던 2 에폭 모델을 저장해서 씁니다."

❓ 그럼 2 에폭만 돌리면 되잖아? 결과적으로는 그렇다. 하지만 몇 에폭이 최적인지는 돌려 봐야 안다. 그래서 넉넉히 돌리고 최적 시점을 저장한다. 검증 손실이 몇 번 연속 안 좋아지면 멈추는 조기 종료(early stopping) 를 쓰면 시간을 아낄 수 있다.


Part 11. 평가와 예측 (셀 65~72)

model.load_state_dict(torch.load('best_model_checkpoint.pth'))   # 2 에폭 모델 불러오기
evaluate(model, valid_dataloader, ...)    # 검증 84.83%
evaluate(model, test_dataloader, ...)     # 테스트 84.72%
  • 테스트 정확도 84.7%. 검증(84.8%)과 거의 같다. 검증 데이터로 고른 모델이 처음 보는 데이터에서도 비슷하게 동작한다는 뜻이다.
def predict(text, model, word_to_index, index_to_tag):
    model.eval()
    tokens = mecab.morphs(text)                                      # 토큰화
    tokens = [word for word in tokens if not word in stopwords]      # 불용어 제거
    token_indices = [word_to_index.get(token, 1) for token in tokens]   # 정수 인코딩 (없으면 1=UNK)
    input_tensor = torch.tensor([token_indices], dtype=torch.long).to(device)   # (1, 길이)
    with torch.no_grad():
        logits = model(input_tensor)
    return index_to_tag[torch.argmax(logits, dim=1).item()]
  • 새 문장 하나도 학습 데이터와 똑같은 전처리를 거친다. 토큰화 → 불용어 → 정수.
  • word_to_index.get(token, 1) → 6부의 try/except를 한 줄로 쓴 것이다.
  • 여기서는 패딩을 안 한다. 배치가 1개라 길이를 맞출 필요가 없다. LSTM은 길이에 상관없이 읽는다(4.1의 가중치 공유).
  • 셀 69~72의 예측: 이 영화 개꿀잼 ㅋㅋㅋ → 긍정, 이딴게 영화냐 ㅉㅉ → 부정 등.
  • 주의: 이 함수는 한글 정제(셀 19의 정규식)를 안 한다. ㅋㅋㅋ는 한글 자모라 남지만, 영어나 숫자가 섞이면 훈련 때와 다른 토큰이 들어간다.

토론거리 — 뒤 패딩 + 마지막 은닉 상태 (시간 남으면)

  • 7부에서 패딩을 뒤에 붙였다. 평균 길이 12인 리뷰라면 진짜 단어 12개를 읽은 뒤, PAD를 18개 더 읽는다.
  • 그런데 9부의 모델은 30번째 시점의 은닉 상태를 쓴다. 즉 "리뷰를 다 읽고 빈칸 18개를 더 읽은 뒤의 기억"으로 분류하는 셈이다.
  • 그래도 84%가 나오는 이유: LSTM이 학습하면서 PAD 임베딩이 들어오면 삭제 게이트를 열고 입력 게이트를 닫는 법을 배운다. 그러면 기억이 거의 그대로 유지된다(4.2의 f≈1,i≈0f \approx 1, i \approx 0).
  • 직접 비교해 봤다. 같은 조건에서 패딩을 앞에 붙이기만 바꿨다(CPU, 시드 0, 각 1회).
1 에폭 검증2 에폭 검증
뒤 패딩 (노트북)83.4%84.9%
앞 패딩84.0%85.0%

차이는 작다. 1회 실행이라 우연일 수도 있다. "LSTM이 PAD를 무시하는 법을 꽤 잘 배운다"는 쪽에 가깝다.

  • 더 깔끔한 방법:
    • nn.Embedding(vocab_size, embedding_dim, padding_idx=0) → PAD를 0 벡터로 고정한다(4.4).
    • pack_padded_sequence → 진짜 길이까지만 LSTM이 읽게 한다.

🗣 "이 모델은 짧은 리뷰를 읽고 나서 빈칸을 십여 개 더 읽은 다음의 기억으로 판단해요. 그래도 잘 되는 건, LSTM이 빈칸에서는 기억을 건드리지 않는 법을 배우기 때문이에요. 지난주 게이트 이야기가 여기서 실제로 쓰이는 셈입니다."


마무리 (1분)

🗣 "오늘 4.1부터 4.4까지 본 게 이 노트북 하나에 다 들어 있어요. 리뷰를 정제하고, Mecab으로 토큰화하고, 단어 집합 번호로 바꾸고, 길이 30으로 패딩합니다. 그다음 임베딩, LSTM, Linear를 거쳐 긍정·부정을 고르고, 테스트에서 84.7%가 나왔어요. 2 에폭부터는 과적합이라 체크포인트로 가장 좋았던 모델을 썼습니다."

shape 치트시트

단계shape
패딩된 입력(배치, 30) 정수
임베딩 후(배치, 30, 100)
LSTM lstm_out(배치, 30, 128) — 안 씀
LSTM hidden(1, 배치, 128)
hidden.squeeze(0)(배치, 128)
fc 출력 (로짓)(배치, 2)
라벨(배치,) 0 또는 1

고쳐 두면 좋은 셀 요약

셀고칠 것이유
0, 1, 25(막히면) python-mecab-koMecab 설치 실패 대비
20, 23x = x.replace('', np.nan)pandas 3에서 inplace가 안 먹음
23str.replace(..., regex=True)pandas 2+에서 정제가 건너뛰어짐
57검증 로더 batch_size=512평가 시간 단축

0개의 댓글