카테고리 없음

스파르타 AI-8기 TIL(11/2) -> NLP의 예시 코드

kimjunki-8 2024. 11. 2. 22:33

GPT를 이용해 NLP의 예시코드를 받아왔습니다.

import re
from collections import Counter
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, Dataset

# 예제 텍스트 데이터
train_data = [
    ("I loved this movie", 1),
    ("This film was terrible", 0),
    ("Amazing story and great acting", 1),
    ("Worst movie ever", 0)
]

def build_vocab(data):
    words = []
    for sentence, _ in data:
        words.extend(re.findall(r'\b\w+\b', sentence.lower()))  # 문장 속 단어 추출
    return {word: i+1 for i, word in enumerate(Counter(words))}

vocab = build_vocab(train_data)
vocab_size = len(vocab) + 1  # 패딩을 위한 추가 토큰

class SentimentDataset(Dataset):
    def __init__(self, data, vocab):
        self.data = data
        self.vocab = vocab

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        sentence, label = self.data[idx]  # 리뷰와 감정 레이블을 가져옴
        words = re.findall(r'\b\w+\b', sentence.lower())  # 단어 추출
        # 단어를 인덱스로 변환 (없는 단어는 0)
        indices = [self.vocab.get(word, 0) for word in words]  
        return torch.tensor(indices), torch.tensor(label)

class LSTMModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
        super(LSTMModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)  # 단어 임베딩 레이어
        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)  # LSTM 레이어
        self.fc = nn.Linear(hidden_dim, output_dim)  # 최종 출력층

    def forward(self, x):
        embedded = self.embedding(x)  # (배치, 시퀀스 길이) -> (배치, 시퀀스 길이, 임베딩 차원)
        lstm_out, _ = self.lstm(embedded)  # LSTM의 출력을 얻음
        hidden_state = lstm_out[:, -1, :]  # 마지막 시퀀스의 출력 상태만 사용
        output = self.fc(hidden_state)  # 선형 변환으로 감정 예측
        return output
        
model = LSTMModel(vocab_size, embed_dim=8, hidden_dim=16, output_dim=2)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(5):
    model.train()
    for sentences, labels in DataLoader(dataset, batch_size=2, shuffle=True):
        optimizer.zero_grad()
        sentences = nn.utils.rnn.pad_sequence(sentences, batch_first=True)
        outputs = model(sentences)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
    print(f"Epoch {epoch+1}, Loss: {loss.item()}")

def predict(model, vocab, sentence):
    words = re.findall(r'\b\w+\b', sentence.lower())
    indices = torch.tensor([vocab.get(word, 0) for word in words]).unsqueeze(0)
    with torch.no_grad():
        output = model(indices)
        _, predicted = torch.max(output, 1)
    return "Positive" if predicted.item() == 1 else "Negative"

print(predict(model, vocab, "What an amazing movie!"))
print(predict(model, vocab, "This was a complete waste of time."))

 


데이터 준비 및 전처리
import re
from collections import Counter

# 예제 텍스트 데이터
train_data = [
    ("I loved this movie", 1),
    ("This film was terrible", 0),
    ("Amazing story and great acting", 1),
    ("Worst movie ever", 0)
]

먼저, 데이터셋 입니다. 각 텍스트와 그 뒤에 숫자는 긍정: 1, 부정: 0로 이루어져 있습니다. 각 문장을 단어 단위로 분리하여 숫자로 표현할 준비를 합니다.

def build_vocab(data):
    words = []
    for sentence, _ in data:
        words.extend(re.findall(r'\b\w+\b', sentence.lower()))  # 문장 속 단어 추출
    return {word: i+1 for i, word in enumerate(Counter(words))}

vocab = build_vocab(train_data)
vocab_size = len(vocab) + 1  # 패딩을 위한 추가 토큰

def build_vocab(data):
build_vocab라는 함수를 정의하고, 매개변수 data를 받습니다. data는 각 텍스트와 해당 레이블(혹은 다른 부가 정보)을 포함하는 튜플의 리스트로 가정됩니다. 이 함수의 목적은 데이터에 등장하는 모든 단어를 모아 고유한 단어별 인덱스를 가진 단어 사전을 만드는 것입니다.

words = []
words라는 빈 리스트를 생성합니다. 이 리스트는 data에 있는 모든 텍스트에서 추출한 단어들을 저장하는 용도로 사용됩니다.

for sentence, _ in data:
data의 각 요소를 순회합니다. 각 요소는 (sentence, _) 형식의 튜플로 구성되어 있으며, sentence는 텍스트 데이터이고 _는 레이블 또는 기타 정보입니다. 언더스코어 _를 사용함으로써 해당 값은 사용하지 않겠다는 의도를 나타냅니다.
즉, data에 있는 문자들(sentence)와 옆 레이블 값(0, 1)에서, 레이블은 아직 필요하지 않으니 _로 공백 처리를 하겠다는 뜻입니다(안 쓰겠다는 뜻).

words.extend(re.findall(r'\b\w+\b', sentence.lower()))
여기선
\b: 단어의 시작 또는 끝.
\w+: 하나 이상의 단어 문자(알파벳, 숫자, 밑줄).
\b: 단어의 끝.
으로 해설할 수 있기 때문에, 단어를 찾는데, 하나 이상의 단어의 시작과 끝을 잡아 한 단어씩 추출해서(findall) words에 넣는다는 뜻입니다(.extend()).

return {word: i+1 for i, word in enumerate(Counter(words))}

Counter(words)는 words 리스트에서 각 단어의 빈도를 계산하여 Counter 객체로 반환합니다. 예를 들어 words가 ['apple', 'banana', 'apple']이라면 Counter({'apple': 2, 'banana': 1})가 생성됩니다.

enumerate(Counter(words))를 통해 Counter 객체의 각 단어와 그 빈도에 대해 인덱스 i와 단어 word를 가져옵니다. 사전 내 단어 인덱스를 1부터 시작하도록 i + 1로 지정합니다. 이렇게 생성된 사전은 각 단어를 키로, 단어의 고유 인덱스를 값으로 하는 딕셔너리가 됩니다

vocab = build_vocab(train_data) 
build_vocab 함수를 호출하여 train_data의 텍스트로부터 단어 사전을 생성합니다. vocab은 단어와 인덱스가 매핑된 딕셔너리가 됩니다.

vocab_size = len(vocab) + 1
vocab_size는 단어 사전의 크기를 저장합니다. len(vocab)은 사전 내 고유 단어 수이며, + 1은 모델에서 사전에 없는 단어를 처리하기 위해 특별한 인덱스를 추가하기 위함입니다.(많은 NLP 모델에서는 입력 시퀀스의 길이를 일정하게 유지하기 위해 패딩을 추가합니다. 이때 패딩 토큰은 보통 인덱스 0으로 예약됩니다. 따라서, 고유 단어 수에 1을 추가하여 패딩을 위한 공간을 확보하는 것입니다.)

즉, vocab에는

{'i': 1, 'loved': 2, 'this': 3, 'movie': 4, 'film': 5, 'was': 6, 'terrible': 7,
 'amazing': 8, 'story': 9, 'and': 10, 'great': 11, 'acting': 12, 'worst': 13, 'ever': 14}

으로 데이터가 담길것이며,
예를 들어 "I loved this movie"는 [1, 2, 3, 4]로 변환될 수 있습니다. vocab_size는 이 사전의 총 크기를 정의하는 데 사용됩니다.

 

.extend()와 re.findall

.extend() 메서드는 파이썬의 리스트 객체에서 사용할 수 있는 메서드로, 주어진 iterable(리스트, 튜플, 문자열 등)의 모든 요소를 리스트의 끝에 추가하는 역할을 합니다. 이 메서드는 원래의 리스트를 수정하며, 반환값은 없습니다 (None을 반환).


구조
list1.extend(iterable)
list1: 
요소를 추가할 리스트입니다.
iterable: 
리스트에 추가할 요소를 포함하는 iterable입니다. 이 iterable의 모든 요소가 list1의 끝에 추가됩니다.


예시:

list1 = [1, 2, 3]
list2 = [4, 5, 6]
list1.extend(list2)  # list1에 list2의 요소를 추가
print(list1)  # 출력: [1, 2, 3, 4, 5, 6]



참고로 튜플도 추가가 가능하며, .append()와 다른점은,  .append()는 주어진 객체를 리스트의 끝에 단일 요소로 추가하는데 반면, .extend()는 iterable의 모든 요소를 추가합니다.
예시:

list1 = [1, 2, 3]
list1.append([4, 5])  # [4, 5]라는 리스트를 단일 요소로 추가
print(list1)  # 출력: [1, 2, 3, [4, 5]]

list1 = [1, 2, 3]
list1.extend([4, 5])  # [4, 5]의 모든 요소를 추가
print(list1)  # 출력: [1, 2, 3, 4, 5]


re.findall()은 파이썬의 re 모듈에 포함된 함수로, 주어진 정규 표현식에 매칭되는 모든 부분 문자열을 찾아 리스트로 반환합니다.
 이 함수는 주어진 문자열에서 정규 표현식과 일치하는 모든 결과를 수집하고, 찾은 결과를 리스트 형태로 반환하여 쉽게 사용할 수 있도록 해줍니다.

구조:
re.findall(pattern, string, flags=0)

pattern: 
검색할 정규 표현식입니다. 찾고자 하는 패턴을 정의합니다.
string: 
정규 표현식을 적용할 입력 문자열입니다.
flags: 
(선택 사항) 정규 표현식의 동작 방식을 수정할 수 있는 플래그입니다. 예를 들어, 대소문자를 무시하려면 re.IGNORECASE 플래그를 사용할 수 있습니다.

예시:
import re

text = "Hello world! This is a test."
words = re.findall(r'\b\w+\b', text)  # 단어를 찾는 정규 표현식
print(words)  # 출력: ['Hello', 'world', 'This', 'is', 'a', 'test']

정규 표현식
r'\b\w+\b':
 이 정규 표현식은 다음과 같은 의미를 가집니다:
\b: 
단어 경계를 나타냅니다. 즉, 단어의 시작이나 끝을 의미합니다.
\w+: 
하나 이상의 단어 문자를 의미합니다. 단어 문자는 알파벳, 숫자, 또는 밑줄(_)로 구성됩니다.
이 정규 표현식은 문장에서 모든 단어를 추출하는 데 사용됩니다.

from torch.utils.data import DataLoader, Dataset

class SentimentDataset(Dataset):
    def __init__(self, data, vocab):
        self.data = data
        self.vocab = vocab

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        sentence, label = self.data[idx]  # 리뷰와 감정 레이블을 가져옴
        words = re.findall(r'\b\w+\b', sentence.lower())  # 단어 추출
        index = [self.vocab.get(word, 0) for word in words]  # 단어를 인덱스로 변환 (없는 단어는 0)
        return torch.tensor(index), torch.tensor(label)
class SentimentDataset(Dataset):

SentimentDataset 클래스는 Dataset 클래스를 상속받아 정의됩니다. 이는 PyTorch가 요구하는 데이터셋 구조를 준수합니다.

def __init__(self, data, vocab):
    self.data = data
    self.vocab = vocab

__init__: 객체가 생성될 때 호출되는 메서드입니다.
data: 리뷰 문장과 해당 감정 레이블의 튜플로 구성된 데이터셋을 전달받습니다. 예를 들어, data는 [(" I loved this movie ", 1), (" Worst movie ever ", 0)]와 같은 형태일 수 있습니다.
vocab: 단어와 해당 인덱스를 매핑하는 사전(dictionary)입니다. 이 사전은 모델이 단어를 인덱스로 변환할 때 사용됩니다.

def __len__(self):
    return len(self.data)

__len__: 데이터셋의 길이를 반환하는 메서드입니다. 이는 데이터셋에 포함된 샘플의 개수를 나타냅니다. len(SentimentDataset) 호출 시 이 메서드가 호출됩니다.

def __getitem__(self, idx):
    sentence, label = self.data[idx]  # 리뷰와 감정 레이블을 가져옴
    words = re.findall(r'\b\w+\b', sentence.lower())  # 단어 추출
    indices = [self.vocab.get(word, 0) for word in words]  # 단어를 인덱스로 변환 (없는 단어는 0)
    return torch.tensor(indices), torch.tensor(label)

__getitem__: 데이터셋의 특정 인덱스 idx에 해당하는 데이터 항목을 반환하는 메서드입니다. 이 메서드는 DataLoader와 함께 사용될 때 호출되어 배치 처리를 가능하게 합니다.
sentence, label = self.data[idx]: 인덱스에 해당하는 리뷰 문장과 그에 대한 감정 레이블을 가져옵니다.
words = re.findall(r'\b\w+\b', sentence.lower()): 문장을 소문자로 변환한 후, 정규 표현식을 사용하여 단어를 추출합니다. \b는 단어 경계를 나타내며, \w+는 하나 이상의 단어 문자를 의미합니다.
indices = [self.vocab.get(word, 0) for word in words]: 추출된 단어들을 인덱스로 변환합니다. self.vocab.get(word, 0)는 단어가 vocab 사전에 없으면 기본값인 0을 반환하여 해당 단어를 인덱스 0으로 처리합니다. 
여기서 get(word, 0)은 파이썬의 딕셔너리(dictionary) 메서드 중 하나로, 특정 키를 사용하여 해당하는 값을 가져오는 방법입니다.

구조:

value = vocab.get(word, 0)

vocab: 이 부분은 단어와 인덱스를 매핑하는 딕셔너리입니다. 예를 들어, {'영화': 1, '좋다': 2, '별로': 3}와 같은 형태로 정의될 수 있습니다.
word: 가져오고자 하는 단어입니다. 예를 들어, "좋다"와 같은 단어를 사용할 수 있습니다.
0: 만약 word가 vocab 딕셔너리에 존재하지 않을 경우 반환할 기본값입니다. 이 경우, 단어에 대한 인덱스가 없기 때문에 0을 반환하게 됩니다.

만약
키가 존재하는 경우: 만약 word가 vocab에 있는 키라면, 해당하는 값을 반환합니다. 예를 들어, vocab.get('좋다', 0)이 호출되면 2가 반환됩니다.
키가 존재하지 않는 경우: 만약 word가 vocab에 없는 키라면, 기본값인 0을 반환합니다. 예를 들어, vocab.get('존재하지 않는 단어', 0)이 호출되면 0이 반환됩니다.

return torch.tensor(indices), torch.tensor(label): 변환된 단어 인덱스 리스트를 텐서로 변환하여 반환합니다. 감정 레이블도 텐서로 변환하여 함께 반환합니다.


 

class LSTMModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
        super(LSTMModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)  # 단어 임베딩 레이어
        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)  # LSTM 레이어
        self.fc = nn.Linear(hidden_dim, output_dim)  # 최종 출력층

    def forward(self, x):
        embedded = self.embedding(x)  # (배치, 시퀀스 길이) -> (배치, 시퀀스 길이, 임베딩 차원)
        lstm_out, _ = self.lstm(embedded)  # LSTM의 출력을 얻음
        hidden_state = lstm_out[:, -1, :]  # 마지막 시퀀스의 출력 상태만 사용
        output = self.fc(hidden_state)  # 선형 변환으로 감정 예측
        return output

 

def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
    super(LSTMModel, self).__init__()
    self.embedding = nn.Embedding(vocab_size, embed_dim)  # 단어 임베딩 레이어
    self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)  # LSTM 레이어
    self.fc = nn.Linear(hidden_dim, output_dim)  # 최종 출력층

vocab_size: 단어 사전의 크기입니다. 즉, 모델이 처리할 수 있는 단어의 총 개수입니다.

embed_dim: 각 단어를 임베딩할 차원 수입니다. 임베딩 차원은 단어의 의미를 표현하는 벡터의 크기를 결정합니다.

hidden_dim: LSTM 레이어의 숨겨진 상태 차원 수입니다. 이는 LSTM의 메모리 용량을 결정합니다.

output_dim: 모델의 최종 출력 차원입니다. 보통 감정 분석의 경우 긍정, 부정 등 감정의 개수에 해당합니다.

super(LSTMModel, self).__init__(): 부모 클래스의 생성자를 호출하여 초기화합니다. 이는 nn.Module의 기본 기능을 사용할 수 있도록 합니다.

self.embedding: 단어 임베딩 레이어를 정의합니다. nn.Embedding(vocab_size, embed_dim)은 각 단어를 embed_dim 차원의 벡터로 변환합니다.

self.lstm: LSTM 레이어를 정의합니다. nn.LSTM(embed_dim, hidden_dim, batch_first=True)는 입력 차원이 embed_dim, 숨겨진 상태의 차원이 hidden_dim인 LSTM을 생성합니다. batch_first=True는 입력 텐서의 첫 번째 차원이 배치 크기가 되도록 설정합니다.

self.fc: 최종 출력층을 정의합니다. nn.Linear(hidden_dim, output_dim)은 LSTM의 출력을 입력으로 받아 output_dim 차원의 출력으로 변환합니다.

def forward(self, x):
    embedded = self.embedding(x)  # (배치, 시퀀스 길이) -> (배치, 시퀀스 길이, 임베딩 차원)
    lstm_out, _ = self.lstm(embedded)  # LSTM의 출력을 얻음
    hidden_state = lstm_out[:, -1, :]  # 마지막 시퀀스의 출력 상태만 사용
    output = self.fc(hidden_state)  # 선형 변환으로 감정 예측
    return output

x: 모델에 입력으로 들어오는 텐서입니다. 일반적으로 배치의 문장 인덱스가 포함된 텐서입니다. 형태는 (배치 크기, 시퀀스 길이)입니다.

embedded = self.embedding(x): 입력 인덱스를 임베딩 레이어를 통해 임베딩 벡터로 변환합니다. 결과는 (배치 크기, 시퀀스 길이, 임베딩 차원) 형태입니다.

lstm_out, _ = self.lstm(embedded): 임베딩된 벡터를 LSTM에 전달하여 출력을 얻습니다. lstm_out은 LSTM의 모든 시퀀스에 대한 출력 상태입니다.

hidden_state = lstm_out[:, -1, :]: LSTM의 출력 중 마지막 시퀀스의 출력 상태를 선택합니다. 이는 LSTM이 문장의 전체 내용을 요약한 것입니다. 형태는 (배치 크기, 숨겨진 차원)입니다.
output = self.fc(hidden_state): 마지막 숨겨진 상태를 선형 변환을 통해 최종 출력을 계산합니다. 결과는 감정 예측을 위한 출력이며, 형태는 (배치 크기, output_dim)입니다.

return output: 최종 출력을 반환합니다. 이 출력은 모델의 예측 결과로, 감정 분석의 경우 각 감정 클래스에 대한 확률 분포로 해석될 수 있습니다.

여기서 x와 lstm_out에 대해 조금 더 자세히 알아보겠습니다.
x는 forward 메서드의 인자이므로, 호출될 때 외부에서 값을 전달받습니다. 다시 말해, x는 직접적으로 선언되거나 초기화되지 않지만, 메서드가 호출될 때 인자로 들어오는 값을 참조합니다.

따라서 x는 명시적으로 선언되지 않지만, forward 메서드가 호출될 때 외부에서 전달된 값(여기서는 sentences)을 통해 값을 가지게 됩니다. 이와 같은 방식은 객체지향 프로그래밍에서 일반적인 패턴입니다. forward 메서드가 호출될 때 인자로 들어오는 값이 매개변수에 할당되는 것이죠.

self.lstm(embedded)는 PyTorch의 LSTM 모듈을 호출하는 부분으로, 주어진 입력 텐서에 대해 LSTM 연산을 수행합니다. 여기서 embedded는 입력으로 사용되는 텐서로, 다음과 같은 형태를 가집니다:
입력 텐서 embedded: (배치 크기, 시퀀스 길이, 임베딩 차원)

LSTM 호출의 반환값

self.lstm(embedded)는 두 개의 값을 반환합니다:
lstm_out (출력): (배치 크기, 시퀀스 길이, 숨겨진 차원)
각 시퀀스의 모든 타임 스텝에 대한 숨겨진 상태를 포함하는 텐서입니다. 각 타임 스텝의 출력은 그 타임 스텝에서의 입력 정보와 이전 타임 스텝의 상태를 기반으로 계산됩니다.
이 값은 LSTM이 처리한 시퀀스의 모든 타임 스텝에 대한 정보가 포함되어 있습니다.

(h_n, c_n) (숨겨진 상태와 셀 상태):
h_n (숨겨진 상태): (레이어 수, 배치 크기, 숨겨진 차원)
마지막 타임 스텝에서의 숨겨진 상태로, 시퀀스의 정보가 요약된 벡터입니다.
후속 시퀀스 처리에 사용되거나 출력층에 입력될 수 있습니다.
c_n (셀 상태): (레이어 수, 배치 크기, 숨겨진 차원)
LSTM의 셀 상태로, LSTM의 장기 기억을 나타냅니다.
이 값도 다음 시퀀스 처리에 사용될 수 있습니다.

하지만 여기서는 lstm_out만 받기로 하고 h_n, c_n은 받지 않기에, _로 표기했습니다.
그래서 그 밑에 [:, -1, :]는 다음을 의미합니다:
: : 배치 차원을 모두 선택합니다. 즉, 모든 배치 샘플에 대해 마지막 타임 스텝을 가져옵니다.
-1 : 시퀀스 길이에서 마지막 타임 스텝을 선택합니다.
: : 숨겨진 차원을 모두 선택하여 각 시퀀스의 마지막 타임 스텝의 전체 숨겨진 상태를 가져옵니다.

model = LSTMModel(vocab_size, embed_dim=8, hidden_dim=16, output_dim=2)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(5):
    model.train()
    for sentences, labels in DataLoader(dataset, batch_size=2, shuffle=True):
        optimizer.zero_grad()
        sentences = nn.utils.rnn.pad_sequence(sentences, batch_first=True)
        outputs = model(sentences)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
    print(f"Epoch {epoch+1}, Loss: {loss.item()}")
model = LSTMModel(vocab_size, embed_dim=8, hidden_dim=16, output_dim=2)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

LSTMModel: 사전 정의된 LSTMModel 클래스를 사용하여 모델을 초기화합니다.
vocab_size: 어휘의 크기, 즉 모델이 처리할 수 있는 단어의 개수.
embed_dim=8: 각 단어를 8차원의 벡터로 임베딩합니다.
hidden_dim=16: LSTM의 숨겨진 상태 벡터의 차원을 16으로 설정합니다.
output_dim=2: 최종 출력층의 차원을 2로 설정하여 감정(예: 긍정/부정)을 두 개의 클래스로 분류합니다.
criterion = nn.CrossEntropyLoss(): 분류 문제에서 사용되는 손실 함수로, 예측 결과와 실제 레이블 간의 차이를 계산합니다.
optimizer = torch.optim.Adam(model.parameters(), lr=0.001): 모델의 매개변수를 업데이트할 Adam 옵티마이저입니다. 학습률은 0.001로 설정됩니다.

for epoch in range(5):
    model.train()

for epoch in range(5): 5번의 에포크(epoch)를 통해 모델을 훈련합니다. 각 에포크는 전체 데이터셋을 한 번씩 학습합니다.
model.train(): 모델을 학습 모드로 전환합니다. 이는 드롭아웃 같은 특정 층의 동작 방식에 영향을 줍니다.

for sentences, labels in DataLoader(dataset, batch_size=2, shuffle=True):

DataLoader: dataset으로부터 미니배치를 생성합니다.
batch_size=2로 한 번에 두 샘플씩 처리합니다.
shuffle=True로 데이터를 매 에포크마다 섞어 학습합니다.
sentences와 labels: SentimentDataset에서 가져온 문장과 레이블입니다.

밑 코드들은 이미 많이  설명했기 때문에 스킵..


def predict(model, vocab, sentence):
    words = re.findall(r'\b\w+\b', sentence.lower())
    indices = torch.tensor([vocab.get(word, 0) for word in words]).unsqueeze(0)
    with torch.no_grad():
        output = model(indices)
        _, predicted = torch.max(output, 1)
    return "Positive" if predicted.item() == 1 else "Negative"

print(predict(model, vocab, "What an amazing movie!"))
print(predict(model, vocab, "This was a complete waste of time."))
문장 전처리 및 인덱스 변환 (words와 indices):
입력된 문장을 단어로 분리하고 소문자로 변환합니다. 예를 들어 "What an amazing movie!"는 ["what", "an", "amazing", "movie"]가 됩니다.
각 단어를 사전에 따라 인덱스로 변환합니다. 예를 들어, ["what", "an", "amazing", "movie"]는 [0, 0, 8, 4]로 변환됩니다. 여기서 "what"과 "an"은 사전에 없으므로 0으로 매핑됩니다.
.unsqueeze(0)는 입력 데이터의 첫 번째 차원에 배치를 추가하여 (1, 시퀀스 길이) 모양으로 만듭니다. 이는 모델에 배치 단위로 입력되도록 하는 역할을 합니다.

추론 모드 전환 (with torch.no_grad()):
torch.no_grad()는 추론 단계에서 모델의 가중치가 업데이트되지 않도록 비활성화합니다. 이를 통해 메모리를 절약할 수 있습니다.

모델 예측 (output = model(indices)):
입력 인덱스를 모델에 통과시켜 예측 결과를 얻습니다. 출력 텐서는 output에 저장되며, 이 값은 (1, 2) 형태의 텐서로, 첫 번째 차원은 배치 크기, 두 번째 차원은 각 클래스에 대한 예측 점수를 의미합니다.

클래스 선택 (_, predicted = torch.max(output, 1)):
torch.max(output, 1)을 통해 예측 점수가 가장 높은 클래스의 인덱스를 얻습니다. 예를 들어, 출력이 [0.3, 1.2]라면, 두 번째 인덱스(1)의 값이 더 크므로 predicted는 tensor([1])이 됩니다.

결과 반환 ("Positive" if predicted.item() == 1 else "Negative"):
predicted.item()이 1이면 긍정, 0이면 부정으로 판단하여 결과를 반환합니다. 예를 들어, "What an amazing movie!"는 "Positive", "This was a complete waste of time."은 "Negative"로 반환될 수 있습니다.

여기까지..