오늘은 과제 제출 전 팀원들과 도전과제를 어떻게 제출할지 논의해보았고 도전과제는 진행한 부분까지만 제출하기로 정하였습니다.
그 뒤 과제 제출 후 간단하게 발표 시연까지 했습니다.
이후 도전과제를 개인적으로 진행하는 시간을 가졌는데 딥러닝 강의 자료에서는 LSTM모델을 정의하는 코드들이 포함 되어있지 않아 인터넷을 뒤져보고 작성하다가 진도가 너무 느렸기에 GPT에 코드를 작성해달라고 한 뒤 분석하는 시간을 가졌습니다.
전처리는 데이터의 특성별로 필요한 과정이 달라 데이터를 분석하는 시선이 더욱 필요해 보였으며
사용하는 모듈들의 기능을 숙지하지 못한 상태로 판단했습니다.
또한 사용할 데이터를 구분하고 모델을 정의하고 평가하는 코드들에 대한 이해도가 매우 낮아 하나씩 찾아보고 왜 이런 과정을 거치는지 사용된 함수들의 정의 등을 정리하며 학습을 진행했습니다.
딥러닝 넷플릭스 리뷰 감정 분석 모델에 대한 코드들을 전체적으로 알아보는 시간이였습니다.
완전히 이해가 된것은 아니지만 아떤 함수를 왜 사용하고 어떤 기능을하는지 알아보는 시간이였구요 오늘 정리한 것을 토대로 내일은 새로운 딥러닝 문제를 해결해보거나 오늘 TIL을 반복 학습해보아야 겟습니다.
모듈들에 대한 설명은 23일차 TIL에서 자세히 정리 하였으니 찾아 보시면 좋을 것같습니다.
tokenizer에 대한 부분은 2가지 방법이 있어 구분하기 위해 정리하였습니다.
get_tokenizer
라이브러리: torchtext
파이프라인에서 사용하기 위해 여러 가지 사전 정의된 토큰화 방식 제공
주로 NLP모델링에서 빠르게 파이프라인 구축에 사용
"basic_english", "spacy" 등 다양한 토큰화 옵션 제공
Tokenizer
라이브러리: keras.preprocessing.text(Keras/TensorFlow)
텍스트 데이터셋의 단어들을 토큰화하고 숫자 시퀀스로 변환하는 기능 제공
주로 딥러닝에서 LSTM, CNN모델 등과 함께 텍스트 데이터를 시퀀스 형태로 입력할 때 사용
- values 메서드
content와 score의 딕셔너리의 키(Keys)와 값(Values) 중 값(Values)를 texts와 labels에 저장 한다.
texts = df['content'].values
labels = df['score'].values
- Train_Test_Split
학습 데이터와 훈련 데이터셋으로 나누는 방법뿐만 아닌
LSTM에서는 train_test_split을 사용할 때 훈련 데이터와 검증 데이터로 분리한다.
train_texts, val_texts, train_labels, val_labels = train_test_split(texts, labels, test_size=0.2, random_state=42)
- get_tokenizer
1. basic_english로 영어 텍스트의 공백과 구두점 기준으로 단어를 구분해 토큰화시켜 문장의 단어목록을 반환하며, 후속 텍스트 데이터 처리 준비 단계이다.
2. yield_tokens는 단어 사전을 만들기 위한 반복자 함수, 각 문장을 토큰화하여 단어 목록을 생성하고 문장에서 나온 모든 단어를 하나씩 반환해 사전에 추가한다.
3. vocab = build_vocab_from_iterator(yield_tokens(train_texts), specials=["<unk>"]):
- yield_token 함수를 사용해 단어를 순차적으로 받으며 이를 통해 단어 사전을 생성
- specials = ["<nuk">]로 정의된 특별 토큰은 사전에 없는 단어를 처리해 사전에 없는 단어는 <unk>로 치환한다.
4. vocab.set_default_index(vocab["<unk>"])
set_default_index 메서드를 통해 사전에 없는 단어가 나올 경우 기본적으로 <unt>토큰의 인덱스로 설정 되게한다.
#text를 토큰화시켜 단어사전에 저장
tokenizer = get_tokenizer("basic_english")
def yield_tokens(texts):
for text in texts:
yield tokenizer(text)
vocab = build_vocab_from_iterator(yield_tokens(train_texts), specials=["<unk>"])
vocab.set_default_index(vocab["<unk>"])
- 커스텀 데이터셋을 정의하는 이유
- pytorch에서는 Dataset클래스를 커스터마이징하여 데이터로더에서 사용한다.
- 각 리뷰 텍스트와 레이블을 직접 불러와 전처리하고 모델이 학습 가능한 형태(토큰 인덱스)로 변환하기 위해서이다.
- class ReviewDataset(Dataset)
- pytorch의 Dataset 클래스를 상속하여 __len__과 __getitem__ 메서드를 구현한다.
- 구현된 커스텀 데이터셋은 데이터 로더를 통해 배치로 나누어 학습에 사용한다.
- def __init__(self, texts, labels, vocab, tokenizer)
- 클래스 인스턴스를 초기화할 때 텍스트 데이터와 레이블 데이터, 단어사전(vocab), 토큰과 함수(tokenizer)를 인자로 받는다.
- self.texts = texts는 리뷰 텍스트 데이터, self.labels = labels는 해당 리뷰의 점수를 저장한다.
- self.vocab = vocab는 텍스트를 숫자 인덱스로 변환하는 데 필요한 단어 사전을 저장한다.
- self.tokenizer = tokenizer는 텍스트를 토큰화하는 데 사용할 토큰화 함수를 저장한다.
- def __len__(self): return len(self.labels)
- 데이터셋의 길이를 반환하여 데이터셋의 샘플 개수를 알려준다.
- 데이터셋의 전체 데이터 크기를 확인할 때 유용하다.
- def __getitem__(self, idx)
- 주어진 인덱스(idx)에 해당하는 텍스트와 레이블 데이터를 반환한다.
- tokens = self.tokenizer(self.texts[idx])는 인덱스에 해당하는 텍스트를 토큰화하여 단어 목록으로 변환한다.
- text_indices = [self.vocab[token] for token in tokens]는 각 토큰을 단어 사전(vocab)을 이용해 숫자 인덱스 형태로 변환한 리스트
- label = self.labels[idx]는 해당 텍스트의 점수를 불러온다
- return torch.tensor(text_indices, dtype=torch.long), torch.tensor(label, dtype=torch.long)
- 텍스트 인덱스( text_indices )와 레이블(label)을 PyTorch 텐서 형태로 변환하여 반환한다.
- text_indices는 사전(vocab)을 통해 숫자 인덱스로 변환된 리스트
- label은 해당 리뷰 텍스트에 매핑된 정답 레이블로 예측 대상이 되는 점수(정수형태로 변환 된다.)
- dtype=torch.long는 데이터 타입을 **64비트 정수형(int64)**으로 지정하는 옵션
#커스텀 데이터셋 정의
class ReviewDataset(Dataset):
def __init__(self, texts, labels, vocab, tokenizer):
self.texts = texts # 텍스트 데이터
self.labels = labels # 레이블 데이터
self.vocab = vocab # 단어 사전
self.tokenizer = tokenizer # 토큰화 함수
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
# 토큰화 후 단어 인덱스로 변환
tokens = self.tokenizer(self.texts[idx])
text_indices = [self.vocab[token] for token in tokens]
label = self.labels[idx]
return torch.tensor(text_indices, dtype=torch.long), torch.tensor(label, dtype=torch.long)
- batch_size = 32
- 모델 학습 및 평가 과정에서 한 번에 처리할 데이터 샘플 수를 설정한다.
- 배치 크기를 조절하면 학습 속도와 메모리 사용량에 영향을 미친다.
- train_dataset = ReviewDataset(train_texts, train_labels, vocab, tokenizer)
- val_dataset = ReviewDataset(val_texts, val_labels, vocab, tokenizer)
- ReviewDataset(train_texts, train_labels, vocab, tokenizer)는 학습 및 검증 데이터셋을 각각 생성하여 DataLoader에서 사용하기 위한 객체
- train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=lambda x: collate_fn(x, vocab))
- val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, collate_fn=lambda x: collate_fn(x, vocab))
- train_loader와 val_loader는 학습과 검증에 사용될 데이터셋을 로딩하는 객체
- shuffle=True는 학습 데이터의 순서를 매 배치마다 무작위로 섞어준다.
- collate_fn=lambda x: collate_fn(x, vocab)는 배치 내 텍스트와 레이블 텐서들을 일정한 크기로 패딩 처리 및 시퀀스 길이를 정렬하여 LSTM에서 효율적인 학습이 가능
#데이터로더 정의
batch_size = 32
train_dataset = ReviewDataset(train_texts, train_labels, vocab, tokenizer)
val_dataset = ReviewDataset(val_texts, val_labels, vocab, tokenizer)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=lambda x: collate_fn(x, vocab))
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, collate_fn=lambda x: collate_fn(x, vocab))
- LSTMClassifier
- 임베딩, LSTM, 완전 연결층으로 이루어진 신경망 구조를 정의한다
- __init__메서드
- vocab_size, embed_dim, hidden_dim, output_dim 등의 매개변수를 통해 모델의 구조와 각 층의 크기를 설정
- super(LSTMClassifier, self).__init__()은 부모 클래스인 nn.Module의 __init__ 메서드를 호출하여 모델이 올바르게 초기화한다.
- self.embedding = nn.Embedding(vocab_size, embed_dim)
- 입력 텍스트를 임베딩 벡터로 변환하고 입력 데이터 길이를 고정한다.
- vocab_size는 단어 사전의 크기
- embed_dim은 각 단어를 백터화할 차원 수를 의미한다.
- self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
- nn.LSTM(embed_dim, hidden_dim, batch_first=True)는 시퀀스 데이터를 처리하는 LSTM 계층을 정의한다.
- hidden_dim은 LSTM 계층의 은닉 상태 크기를 의미
- batch_first=True를 설정하여 배치 크기와 시퀀스 길이의 순서를 (batch, seq_len, feature_dim) 형태로 설정
- self.fc = nn.Linear(hidden_dim, output_dim)
- 최종적으로 은닉 상태에서 피처를 추출한 후, 각 클래스를 예측하기 위한 완전 연결층
- output_dim은 분류할 클래스의 수로 설정
- def forward(self, text)
- 입력 텍스트 텐서를 받아 예측 결과를 생성하는 과정
- embedded = self.embedding(text)
- 텍스트 인덱스를 임베딩 벡터로 변환하여 단어의 의미를 반영한 벡터로 변환한다.
- _, (hidden, _) = self.lstm(embedded)
- LSTM 계층을 통해 피처를 추출하며, 이때 hidden은 마지막 시퀀스의 은닉 상태를 나타낸다.( 이 은닉 상태는 텍스트의 전체적인 의미를 요약한 값으로 사용된다.
- return self.fc(hidden[-1])
hidden[-1]은 마지막 은닉 상태로 텍스트의 최종 피처를 푠현한다. 이를 완전 연결층에 전달하여 각 클래스로 분류하는 최종 출력이 생성된다.
# 모델 정의
class LSTMClassifier(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
super(LSTMClassifier, 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, text):
embedded = self.embedding(text) # 텍스트를 임베딩
_, (hidden, _) = self.lstm(embedded) # LSTM으로 피처 추출
return self.fc(hidden[-1]) # 마지막 hidden state를 사용해 출력
- vocab_size = len(vocab)
- 사전 내의 단어 개수를 나타내며, nn.Embedding 층에서 임베딩 벡터를 생성할 때 필요한 값이다.
- 임베딩 층은 단어의 고유 인덱스를 사용해 각 단어를 고정된 벡터 차원으로 표현하므로 단어의 총 개수를 알아야한다.
- embed_dim = 64
- 임베딩 벡터의 차원을 설정한다.
- 임베딩 차원이 높을수록 단어의 표현력이 증가할 수 있지만 지나치게 크면 연산량이 커지므로 적절한 값을 설정해야한다.
- hiddden_dim = 128
- LSTM의 은닉층 차원을 설정한다.
- 은닉층 차원이 클수록 LSTM이 더 많은 패턴을 학습할 수 있지만, 연산 비용이 높아질 수 있어 데이터에 맞춰 설정해야한다.
- output_dim = len(label_encoder.classes_)
- 모델의 최종 출력 차원, 즉 분류할 클래스의 개수를 의미한다
- label_encoder.classes_는 각 레이블을 고유 클래스 인덱스로 변환하여 사용하고 이 값을 통해 분류할 클래스 수를 자동으로 설정합니다.
- model = LSTMClassifier(vocab_size, embed_dim, hidden_dim, output_dim)
- 위에서 설정한 하이퍼파라미터 값을 바탕으로 LSTMClassifier 모델을 인스턴스화시킨다.
- 각 하이포파라미터가 모델의 구성에 반영되어 모델 초기화 시점에 필요한 모든 설정이 완료된다.
- 모델 초기화는 이후 훈련 시 파라미터가 업데이트될 기준값을 절정하는 역할도 한다.
- criterion = nn.CrossEntropyLoss()
- 다중 클래스 분류 문제에서 주로 사용되는 손실 함수로, 예측값과 실제값 간의 차이를 로그 확률 기반으로 계산한다.
- 이 함수는 각 클래스의 확률값을 고려하여 손실을 계산하므로, 분류 문제에서 최적화가 잘 이루어진다.
- 예측이 정답에 가까울수록 손실이 작아지고, 예측이 틀릴수록 손실이 커진다.
optimizer = optim.Adam(model.parameters(), lr=0.001)
- Adam 옵티마이저는 경사하강법의 변형으로 학습률을 자동 조정하는 기능이 있어, 일반적으로 빠르고 안정적인 학습을 가능하게 한다.
- model.parameters()는 모델의 모든 학습 가능한 파라미터를 가져와 옵티마이저가 이들을 업데이트할 수 있게한다.
- lr=0.001는 학습률로, Adam 옵티마이저의 초기 학습 속도를 결정한다.
-일반적으로 0.001이나 0.0001로 설정한다.(수렴이 불안정해지는걸 방지)
# 하이퍼파라미터 정의
vocab_size = len(vocab)
embed_dim = 64
hidden_dim = 128
output_dim = len(label_encoder.classes_) # 분류 클래스 개수
# 모델 초기화
model = LSTMClassifier(vocab_size, embed_dim, hidden_dim, output_dim)
# 7. 손실 함수와 옵티마이저 정의
criterion = nn.CrossEntropyLoss() # 분류 문제이므로 CrossEntropyLoss 사용
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam 옵티마이저
- epochs = 10
- 전체 학습을 반복할 횟수로, 모델이 데이터를 몇 번 볼지 설정한다.
- 에포크 수가 높으면 과적합이 발생할 수 있다.
- for epoch in range(epochs)
- 지정한 에포크 수 만큼 학습을 반복하여 모델의 성능을 점진적으로 개선한다.
- 각 에포크는 모델이 모든 학습 데이터를 한번 순회하는 과정을 의미한다.
- model.train()
- 모델을 학습모드로 전환하여 필요 동작을 적용한다.
- 모델이 평가 모드(model.eval())일 때와 다르게, 학습 시에는 드롭아웃과 같은 규제 항목이 적용되도록 설정한다.
- for batch_text, batch_label in train_loader
- train_loader로부터 배치 단위로 데이터를 불러와 모델이 학습할 수 있도록한다.
- batch_text는 텍스트 데이터, batch_label은 해당 텍스트의 레이블이다.
- 배치 처리를 통해 메모리를 효율적으로 사용하고 학습 속도를 향상시킬 수 있다.
- optimizer.zero_grad()
- 이전 배치에서 계산된 기울기를 초기화한다.
- 매 배치마다 새로운 기울기를 계산해야하므로 이전 기울기가 누적되는 것을 방지하기위해 초기화한다.
- output = model(batch_text)
- 현재 배치의 텍스트 데이터를 모델에 입력하여 예측값을 생성한다.
- LSTM을 통해 각 텍스트 시퀀스에서 피처를 추출하고 출력층을 통해 분류 예측이 이루어진다.
- loss = criterion(output, batch_label)
- 예측값(output)과 실제 레이블(batch_label) 간의 손실을 계산한다.
- 손실 함수 criterion으로는 CrossEntropyLoss가 사용되어, 예측값이 실제값과 얼마나 차이가 나는지를 확률적 로그 손실 형태로 계산한다.
- loss.backward()
-손실 값에 대한 기울기를 계산하여 각 모델 파라미터에 대해 **기울기(gradient)**를 구한다.
- 역전파를 통해 기울기가 계산되면, 이 기울기를 사용하여 파라미터가 업데이트된다.
- optimizer.step()
- 계산된 기울기를 바탕으로 파라미터를 업데이트하여 모델을 개선한다.
- optimizer는 Adam 옵티마이저로 설정되어 있어, 파라미터가 기울기를 따라 학습률에 맞춰 갱신한다.
- 최적화 과정이 반복될수록 모델이 더 나은 예측을 하도록 학습한다.
# 모델 학습
epochs = 10
for epoch in range(epochs):
model.train()
for batch_text, batch_label in train_loader:
optimizer.zero_grad() # 기울기 초기화
output = model(batch_text) # 모델 예측
loss = criterion(output, batch_label) # 손실 계산
loss.backward() # 역전파
optimizer.step() # 가중치 갱신
model.eval()
- 모델을 평가 모드로 전환
with torch.no_grad()
- 기울기 계산을 비활성화하는 컨텍스트
- 모델의 파라미터를 업데이트하지 않기 때문에 기울기를 계산할 필요가 없다.
- 메모리 사용량과 연산 비용이 줄어들어 검증 속도가 향상된다.
for batch_text, batch_label in val_loader
- 검증 데이터를 배치 단위로 불러와 반복 처리한다.
- val_loader는 검증 데이터셋을 로드하며, batch_text는 텍스트 데이터, batch_label은 해당 텍스트의 실제 레이블
output = model(batch_text)
- 현재 배치의 텍스트 데이터를 입력받아 모델 예측값을 생성한다.
- output은 예측 결과로, 각 클래스에 대한 확률 점수가 포함된 텐서
pred = output.argmax(1)
- 가장 높은 확률을 가진 클래스 인덱스를 선택한다.
- argmax(1)는 각 행에서 최대값을 가진 인덱스를 반환하므로, 이 값을 예측된 클래스 레이블로 사용한다.
- 모델이 해당 텍스트 데이터에 대해 가장 확률이 높은 클래스를 예측하도록 한다.
correct += (pred == batch_label).sum().item()
- (pred == batch_label).sum().item()는 예측값과 실제 레이블이 일치하는 경우의 개수를 계산한다.
- pred == batch_label는 예측과 실제 레이블을 비교하여 일치 여부를 반환하고, .sum()은 일치하는 예측의 총 개수를 계산한다.
- .item()은 결과를 텐서에서 정수로 변환하여 누적된 correct에 추가한다.
accuracy = correct / len(val_dataset)
- 검증 데이터 전체에 대한 정확도를 계산한다.
- correct는 정확하게 예측된 데이터 수이고, len(val_dataset)는 전체 검증 데이터 수
- 에포크 별 검증 정확도를 출력하여 모델의 성능 변화를 확인한다.
# 검증 단계
model.eval()
correct = 0
with torch.no_grad():
for batch_text, batch_label in val_loader:
output = model(batch_text)
pred = output.argmax(1) # 예측 클래스
correct += (pred == batch_label).sum().item()
accuracy = correct / len(val_dataset)
print(f"Epoch {epoch+1}, Validation Accuracy: {accuracy:.2f}")
1. get_tokenizer는 전처리 과정이라고 볼수있나요?
- 전처리 과정으로 볼 수 있다.
2. text_indices = [self.vocab[token] for token in tokens]는 각 토큰을 단어 사전(vocab)을 이용해 숫자 인덱스 형태로 변환한 리스트라는데 vocab은 texts의 토큰들을 넣은 단어사전이라고 이해했는데 여기서 언제 숫자 인덱스 형태로 변환한 건지 모르겟습니다.? 혹시 tensor로 변환을 하는 건가요?
- vocab은 텍스트에 사용된 모든 단어(토큰)를 고유한 숫자 인덱스로 매핑한 사전입니다. 이 과정은 문자를 숫자로 매핑하는 작업
3. super(LSTMClassifier, self).__init__()은 부모 클래스인 nn.Module의 __init__ 메서드를 호출하여 모델이 올바르게 초기화한다.에서 부모클래스를 말하는게 커스텀 데이터셋을 말하는 건가요?
- 아니다. 부모 클래스는 nn.Module입니다. LSTMClassifier는 nn.Module을 상속받아 만들어진 자식 클래스이다.
- 부모 클래스의 __init__ 메서드를 호출하여 LSTMClassifier가 nn.Module의 모든 기능을 올바르게 상속받고 초기화하게 해준다.
4. model.train()에서 드롭아웃이라는 것이 무엇인가요?
model.train()과 model.eval()은 학습 및 평가 과정에서 모델의 안정적 성능을 유지하도록 돕는 중요한 설정
- 학습 모드 (model.train())
- model.train()은 모델이 학습 중임을 명시합니다.
- 학습 모드에서는 모델이 데이터를 학습하며, 일반화 성능을 높이기 위해 규제 기법(regularization)을 적용합니다.
- 대표적인 규제 기법에는 드롭아웃과 배치 정규화(Batch Normalization)가 있습니다.
- 평가 모드 (model.eval())
- model.eval()은 모델이 평가나 추론 중임을 명시합니다.
- 이 모드에서는 모델의 파라미터 업데이트 없이 예측 성능만 측정하게 되므로 규제 기법이 적용되지 않습니다.
- 드롭아웃과 배치 정규화는 평가 시점에서는 불필요하며, 오히려 정확도를 떨어뜨릴 수 있어 이들 기능이 비활성화됩니다.
- 드롭아웃 (Dropout)
- 드롭아웃은 학습 중에 임의로 뉴런을 비활성화하여, 네트워크가 특정 뉴런에 과도하게 의존하지 않도록 방지하는 기법
- 배치 정규화 (Batch Normalization)
- 배치 정규화는 각 배치마다 평균과 분산을 기준으로 데이터를 정규화하여, 학습 안정성과 속도를 높이는 기법
5. 패딩이란?
- 머신러닝이나 딥러닝에서 입력 데이터의 길이를 동일하게 맞추기 위해 사용하는 방법이다.
- LSTM이나 CNN과 같은 모델은 입력으로 고정된 길이의 텍스트가 필요합니다. 따라서 짧은 텍스트에 추가적인 "패딩 토큰"을 붙여서 길이를 맞추게 됩니다.
- 패딩을 추가해 모든 텍스트 길이를 동일하게 맞추면, 배치를 만들어 처리하기 쉬워지고, 모델이 고정된 입력 크기에서 학습할 수 있게된다.
'TIL' 카테고리의 다른 글
내일배움캠프 26일차 TIL + AI모델 활용 (1) | 2024.11.01 |
---|---|
내일배움캠프 25일차 TIL + 도전과제, AI모델 활용 (2) | 2024.10.31 |
내일배움캠프 23일차 TIL + 취업준비, 도전과제, GitHub (4) | 2024.10.29 |
내일배움캠프 22일차 TIL + 기초 수학 특강, 과제준비 (1) | 2024.10.28 |
내일배움캠프 21일차 TIL + 문제은행, 기초수학 특강 (1) | 2024.10.25 |