자연어처리 수업 도중 과제가 나와서 LLM으로 감정분석 을 해볼수 있는 기회가 생겼다.
가장 먼저 sentiment analysis란 텍스트 데이터에서 감정 상태를 식별하는 자연어 처리의 중요한 과제이다. 오늘은 BERT를 사용해 감정 분석을 수행하고 그 성능을 검증해 보겠다. 모델의 코드 설명, 성능 지표 분석, 모델 특성 및 성능 비교를 포함한다.
BERT 모델을 사용한 감정 분석
데이터 전처리
BERT 모델의 경우, 텍스트 데이터를 전처리하여 모델에 적합한 형식으로 변환한다.
전처리 단계는 다음과 같은 이유로 수행된다.
l URL 제거: 텍스트 데이터에 포함된 URL은 감정 분석에 불필요한 잡음이 될 수 있으므로 제거
l 멘션 제거: 소셜 미디어 텍스트에서 멘션(`@username`)은 감정 분석과 관련 없는 정보이므로 제거
l 해시태그 제거: 해시태그(`@hashtag`)도 감정 분석과 직접 관련이 없으므로 제거
l 숫자 제거: 숫자는 감정 분석과 관계가 적기 때문에 제거
l 특수 문자 제거: 특수 문자는 텍스트 처리 시 잡음이 되므로 제거
l 중복 공백 제거 및 앞뒤 공백 제거: 텍스트를 깔끔하게 만들기 위해 중복 공백과 양쪽의 공백을 제거
l 소문자로 변환: 텍스트의 일관성을 위해 모든 문자를 소문자로 변환
l 불용어 제거: 감정 분석에 도움이 되지 않는 일반적인 단어들을 제거
import re
import nltk
from nltk.corpus import stopwords
nltk.download('stopwords')
def clean_text(text):
if not isinstance(text, str):
return ""
text = re.sub(r'http\S+', '', text) # URL 제거
text = re.sub(r'@\w+', '', text) # 멘션 제거
text = re.sub(r'#\w+', '', text) # 해시태그 제거
text = re.sub(r'\d+', '', text) # 숫자 제거
text = re.sub(r'[^\w\s]', '', text) # 특수 문자 제거
text = re.sub(r'\s+', ' ', text).strip() # 중복 공백 제거 및 앞뒤 공백 제거
text = text.lower() # 소문자로 변환
words = text.split()
filtered_words = [word for word in words if word.lower() not in stopwords.words('english')]
filtered_text = ' '.join(filtered_words)
return text
df_train['text'] = df_train['text'].apply(clean_text)
이러한 전처리 과정을 통해 텍스트 데이터를 정제하고, 모델이 중요하지 않은 정보를 무시하고 중요한 패턴을 학습할 수 있도록 도와준다.
World Cloud 시각화
또한, 감정분석과 관계없는 중복되는 단어를 제거해주기 위해 dataset에 존재하는 text들을 워드클라우드를 사용해 시각화 하여 없앨 단어들을 정하여 삭제해주었다.
레이블 인코딩
다음으로, 감정 레이블을 정수 인덱스로 변환한다. 이는 모델이 감정 레이블을 숫자로 인식하고 처리할 수 있게 한다. 각 감정 레이블을 고유한 숫자에 매핑하여 모델 입력 데이터로 사용한다. Test_data도 동일하게 레이블링.
possible_labels = df_test.sentiment.unique()
possible_labels
label_dict = {}
for index, possible_label in enumerate(possible_labels):
label_dict[possible_label] = index
label_dict
label_dict = {'neutral': 0, 'negative': 1, 'positive': 2}
df_train['label'] = df_train['sentiment'].replace(label_dict)
df_test['label'] = df_test.sentiment.replace(label_dict)
토크나이저 로드
from transformers import BertTokenizer
from torch.utils.data import TensorDataset
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
# 토큰화 함수 정의
def tokenize(batch):
return tokenizer(batch, padding=True, truncation=True)
# 데이터 토큰화
train_encodings = tokenizer(list(df_train['text']), truncation=True, padding=True)
val_encodings = tokenizer(list(df_test['text']), truncation=True, padding=True)
import torch
encoded_data_train = tokenizer.batch_encode_plus(
df_train.text.values,
add_special_tokens=True,
return_attention_mask=True,
pad_to_max_length=True,
max_length=256,
return_tensors='pt'
)
encoded_data_test = tokenizer.batch_encode_plus(
df_test.text.values,
add_special_tokens=True,
return_attention_mask=True,
pad_to_max_length=True,
max_length=256,
return_tensors='pt'
)
input_ids_train = encoded_data_train['input_ids']
attention_masks_train = encoded_data_train['attention_mask']
labels_train = torch.tensor(df_train.label.values)
input_ids_test = encoded_data_test['input_ids']
attention_masks_test = encoded_data_test['attention_mask']
labels_test = torch.tensor(df_test.label.values)
train_data = TensorDataset(input_ids_train, attention_masks_train, labels_train)
test_data = TensorDataset(input_ids_test, attention_masks_test, labels_test)
BERT 모델 학습과 평가
BERT(Bidirectional Encoder Representations from Transformers) 모델을 사용하여 감정 분석을 수행한다. BERT는 트랜스포머 아키텍처를 기반으로 하여 문맥을 양방향으로 이해할 수 있는 강력한 모델이다. BERT 모델 학습 과정은 다음과 같다:
l 토크나이저 초기화: `BertTokenizer`를 사용하여 텍스트 데이터를 토큰으로 변환.
l 데이터셋 클래스 정의: PyTorch `Dataset` 클래스를 상속받아 텍스트와 레이블을 담은 커스텀 데이터셋을 정의.
l 데이터로더: `DataLoader`를 사용하여 배치 학습을 수행할 수 있도록 데이터셋을 준비.
l 모델 초기화: `BertForSequenceClassification`을 사용하여 BERT 모델을 초기화하고 출력 레이블 수를 설정.
l 최적화기: Adam 최적화기를 사용하여 모델의 파라미터를 업데이트.
import torch
from transformers import BertForSequenceClassification, AdamW, get_linear_schedule_with_warmup
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler
model = BertForSequenceClassification.from_pretrained("bert-base-uncased",
num_labels=len(label_dict),
output_attentions=False,
output_hidden_states=False)
batch_size = 16
dataloader_train = DataLoader(train_data,
sampler=RandomSampler(train_data),
batch_size=batch_size)
dataloader_test = DataLoader(test_data, batch_size=batch_size, shuffle=True)
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import numpy as np
# GPU 사용 설정 (가능한 경우)
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
model.to(device)
# 옵티마이저 및 학습률 스케줄러 설정
optimizer = AdamW(model.parameters(), lr=2e-5, eps=1e-8)
epochs = 4
total_steps = len(train_data) * epochs
scheduler = get_linear_schedule_with_warmup(optimizer,
num_warmup_steps=0,
num_training_steps=total_steps)
# 평가 함수 정의
def f1_score_func(preds, labels):
preds_flat = np.argmax(preds, axis=1).flatten()
labels_flat = labels.flatten()
return f1_score(labels_flat, preds_flat, average='weighted')
def accuracy_per_class_and_overall(preds, labels):
# Invert the label dictionary to map labels to class names
label_dict_inverse = {v: k for k, v in label_dict.items()}
# Flatten the predictions and labels
preds_flat = np.argmax(preds, axis=1).flatten()
labels_flat = labels.flatten()
total_correct = 0
total_count = 0
for label in np.unique(labels_flat):
y_preds = preds_flat[labels_flat == label]
y_true = labels_flat[labels_flat == label]
class_correct = len(y_preds[y_preds == label])
class_total = len(y_true)
total_correct += class_correct
total_count += class_total
print(f'Class: {label_dict_inverse[label]}')
print(f'Accuracy: {class_correct}/{class_total} ({class_correct/class_total:.2%})\n')
# Calculate overall accuracy
overall_accuracy = total_correct / total_count
print(f'Overall Accuracy: {total_correct}/{total_count} ({overall_accuracy:.2%})\n')
return overall_accuracy
모델 학습 단계에서는 각 배치에 대해 손실을 계산하고 역전파를 통해 파라미터를 업데이트한다.
def evaluate(dataloader_val):
model.eval()
loss_val_total = 0
predictions, true_vals = [], []
for batch in dataloader_val:
batch = tuple(b.to(device) for b in batch)
inputs = {'input_ids': batch[0],
'attention_mask': batch[1],
'labels': batch[2],
}
with torch.no_grad():
outputs = model(**inputs)
loss = outputs[0]
logits = outputs[1]
loss_val_total += loss.item()
logits = logits.detach().cpu().numpy()
label_ids = inputs['labels'].cpu().numpy()
predictions.append(logits)
true_vals.append(label_ids)
loss_val_avg = loss_val_total/len(dataloader_val)
predictions = np.concatenate(predictions, axis=0)
true_vals = np.concatenate(true_vals, axis=0)
return loss_val_avg, predictions, true_vals
for epoch in tqdm(range(1, epochs+1)):
model.train()
loss_train_total = 0
progress_bar = tqdm(dataloader_train, desc='Epoch {:1d}'.format(epoch), leave=False, disable=False)
for batch in progress_bar:
model.zero_grad()
batch = tuple(b.to(device) for b in batch)
inputs = {'input_ids': batch[0],
'attention_mask': batch[1],
'labels': batch[2],
}
outputs = model(**inputs)
loss = outputs[0]
loss_train_total += loss.item()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
progress_bar.set_postfix({'training_loss': '{:.3f}'.format(loss.item()/len(batch))})
torch.save(model.state_dict(), f'finetuned_BERT_epoch_{epoch}.model')
tqdm.write(f'\nEpoch {epoch}')
loss_train_avg = loss_train_total/len(dataloader_train)
tqdm.write(f'Training loss: {loss_train_avg}')
이후, 학습이 완료된 평가 모드로 전환하여 테스트 데이터셋에 대해 예측을 수행. 평가 지표로는 정확도(accuracy)와 `classification_report`를 사용하여 모델의 성능을 분석한다.
...다음에는 순환 신경망, LSTM을 사용하여 감정분석을 해보겠다.
'NLP' 카테고리의 다른 글
[논문 뜯어보기] DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning (2) | 2025.02.14 |
---|---|
RNN 사용해서 감정 분석 해보기 (0) | 2024.06.20 |
챗봇 구현을 위한 데이터로더 기능 테스트 (7) | 2024.05.22 |
Transformer로 간단한 챗봇 구현 및 평가 해보기 (2) | 2024.05.02 |
Transformer 데이터 전처리 해보기 (5) | 2024.05.01 |