1. CBOW Text Classification
"I like studying data analysis." 라는 문장이 주어져있다고 합시다.
저희는 studying 이라는 단어의 정보를 주변의 단어의 정보로부터 얻고자 합니다.
CBOW 방법은 "studying" 을 주변 단어인 "I", 'like", "data", "analysis" 로 부터 유추됩니다.
우선 첫 번째 과정으로, "I", 'like", "data", "analysis" 단어들에게서 정보를 추출해야합니다.
정보를 추출한다는 것은 각 단어들을 vector화 시키는 것입니다.
각 단어들을 vector화 시키기 위해서 우선 가장 기본적인 방법으로 one-hot encoding을 사용합니다.
그리고, look-up table 이라고 불리는 행렬에 곱함으로써 원하는 차원으로의 embedding이 진행됩니다.
◼ import
import urllib.request
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim
from copy import deepcopy
from torch.utils.data import Dataset, DataLoader
from tqdm.auto import tqdm
|
◼ 파일 가져오기
urllib.request.urlretrieve('https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt', filename='ratings_train.txt' )
urllib.request.urlretrieve('https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt', filename='ratings_test.txt' )
train_dataset = pd.read_table('ratings_train.txt')
train_dataset
|
◼ train_dataset 값 확인
# pos, nag 비율
train_dataset['label'].value_counts()
# 결측값(null 값)의 개수 sum(train_dataset['document'].isnull())
# 각 값이 결측값인지 여부 ~train_dataset['document'].isnull()
|
◼ 결측값 삭제
# 결측값삭제 train_dataset = train_dataset[~train_dataset['document'].isnull()]
# 결측값 확인: 0 sum(train_dataset['document'].isnull())
# 데이터프레임 확인 (5개 행 삭제) train_dataset
|
0 ---------------------------------------- |
◼ 1행의 문자열: 공백을 기준으로 분리(split)
train_dataset['document'].iloc[0].split()
|
['아', '더빙..', '진짜', '짜증나네요', '목소리'] |
◼ Tokenization: 자연어를 숫자의 형식으로 변형시켜야 함
◼ (vocab) : 추출된 모든 고유한 단어
vocab = set()
for doc in train_dataset['document']:
for token in doc.split():
vocab.add(token)
len(vocab)
|
357862 |
◼ { vocab_cnt_dict } | (단어 : 등장 횟수)
# 단어의 빈도수 구하기
vocab_cnt_dict = {}
for doc in train_dataset['document']:
for token in doc.split():
if token not in vocab_cnt_dict:
vocab_cnt_dict[token] = 0
vocab_cnt_dict[token] += 1
vocab_cnt_list = [(token, cnt) for token, cnt in vocab_cnt_dict.items()]
# 처음부터 10개의 원소를 가져와 출력 vocab_cnt_list[:10]
|
[('아',1204), ('더빙..',.2), ('진짜', 5929), ('짜증나네요', 10) ('목소리', 99), ... ] |
◼ { vocab_cnt_dict } 딕셔너리(dictionary)에서
(단어 : 등장 횟수) 를 튜플로 묶어 리스트(list)로 만들기
vocab_cnt_list = [(token, cnt) for token, cnt in vocab_cnt_dict.items()]
# 처음부터 10개의 원소를 가져와 출력 vocab_cnt_list[:10]
|
[('아', 1204), ('더빙..', 2), ('진짜', 5929), ('짜증나네요', 10), ('목소리', 99), ('흠...포스터보고', 1), ('초딩영화줄....오버연기조차', 1), ('가볍지', 17), ('않구나', 2), ('너무재밓었다그래서보는것을추천한다', 1)] |
|
◼ [ vocab_cnt_list ] 등장 횟수 내림차순 정렬
등장횟수가 가장 적은 top 10 출력
# 등장횟수 내림차순으로 정렬 top_vocabs = sorted(vocab_cnt_list, key=lambda tup: tup[1], reverse=True)
# 처음부터 10개의 원소를 가져와 출력
vocab_cnt_list[:10]
|
[('영화', 10825), ('너무', 8239), ('정말', 7791), ('진짜', 5929), ('이', 5059), ('영화.', 3598), ('왜', 3285), ('더', 3260), ('이런', 3249), ('그냥', 3237), ...... ('본다면', 105), ('최고다.', 105), ('이유를', 105), ('웃음이', 105), ('ㅡㅡ;', 105), ('로맨틱', 105), ('같아', 105), ...] --------------------------------------------------------------------- |
◼ 단어 등장 횟수 평균값 , top 10 출력
# 단어 등장 횟수 리스트로 저장 cnts = [cnt for _, cnt in top_vocabs]
# 단어 등장 횟수 평균값 np.mean(cnts)
# 처음부터 10개의 원소 출력
cnts[:10]
|
|
|
◼ n_vocab | 단어 등장 횟수가 3 이상인 단어 개수
# cnts 리스트로 변환, 등장횟수가 3이상인 단어 개수 sum(np.array(cnts) > 2)
# 결과를 n_vocab에 저장 n_vocab = sum(np.array(cnts) > 2)
n_vocab
|
|
|
◼ [ top_vocabs_truncated ] top_vocabs에서 처음 n_vocab 개의 항목을 추출해 할당
리스트의 처음 5개 항목을 출력
top_vocabs_truncated = top_vocabs[:n_vocab]
top_vocabs_truncated[:5]
|
[('영화', 10825), ('너무', 8239), ('정말', 7791), ('진짜', 5929), ('이', 5059)] |
◼ [ vocabs ] top_vocabs_truncated 리스트에서 각 요소의 첫 번째 요소를 추출하여 할당,
처음 5개 항목을 출력
vocabs = [token for token, cnt in top_vocabs_truncated]
vocabs[:5]
|
['영화', '너무', '정말', '진짜', '이'] |
2. special token
- [UNK] : Unkonown token
- [PAD] : Padding token
◼ [UNK]와 [PAD] 토큰이 어휘 목록인 vocabs에 포함되어 있는지를 확인
unk_token = '[UNK]'
unk_token in vocabs
pad_token = '[PAD]'
pad_token in vocabs
|
|
False ------------ False |
◼ [ vocabs ] 맨 앞에 unk_token과 pad_token을 삽입
리스트의 처음 5개 항목을 출력
vocabs.insert(0, unk_token)
vocabs.insert(0, pad_token)
vocabs[:5]
|
['[PAD]', '[UNK]', '영화', '너무', '정말'] |
◼ [ vocabs ]를 사용하여 두 개의 매핑을 생성
[ idx_to_token ] 인덱스를 토큰으로 매핑
[ token_to_idx ] 토큰을 인덱스로 매핑하는 사전
idx_to_token = vocabs
token_to_idx = {token: i for i, token in enumerate(idx_to_token)}
|
◼클래스 Tokenizer | 텍스트를 토큰 ID의 리스트로 변환
단어 사전(vocabs)을 사용하여 토큰을 인덱스로 매핑하고, 선택적으로 패딩을 적용
class Tokenizer:
def __init__(self, vocabs, use_padding=True, max_padding=64, pad_token='[PAD]', unk_token='[UNK]'):
self.idx_to_token = vocabs
self.token_to_idx = {token: i for i, token in enumerate(self.idx_to_token)}
self.use_padding = use_padding
self.max_padding = max_padding
self.pad_token = pad_token
self.unk_token = unk_token
self.unk_token_idx = self.token_to_idx[self.unk_token]
self.pad_token_idx = self.token_to_idx[self.pad_token]
def __call__(self, x:str):
token_ids = []
token_list = x.split()
for token in token_list:
if token in self.token_to_idx:
token_idx = self.token_to_idx[token]
else:
token_idx = self.unk_token_idx
token_ids.append(token_idx)
if self.use_padding:
token_ids = token_ids[:self.max_padding]
n_pads = self.max_padding - len(token_ids)
token_ids = token_ids + [self.pad_token_idx] * n_pads
return token_ids
tokenizer = Tokenizer(vocabs, use_padding=False)
|
|
◼ train_dataset에서 첫 번째 문서를 가져와 출력
이를 tokenizer를 사용하여 토큰 ID 리스트로 변환
sample = train_dataset['document'].iloc[0]
print(sample)
tokenizer(sample) # [51, 1, 5, 10485, 1064]
|
◼ train_dataset의 'document' 열에 있는 모든 문서를 순회하면서 각 문서를 토큰화
토큰 리스트의 길이를 [ token_length_list ] 에 저장
token_length_list = []
for sample in train_dataset['document']:
token_length_list.append(len(tokenizer(sample)))
|
◼ [ token_length_list ] 토큰 길이의 분포를 시각화
plt.hist(token_length_list)
plt.xlabel('token length')
plt.ylabel('count')
|
◼ 최대 토큰 길이
max(token_length_list)
|
41 |
◼ 최대 길이 50으로 패딩을 추가하는 토크나이저를 생성
tokenizer = Tokenizer(vocabs, use_padding=True, max_padding=50)
print(tokenizer(sample))
|
|
◼ 훈련 및 검증 데이터 불러오기
train_valid_dataset = pd.read_table('ratings_train.txt')
test_dataset = pd.read_table('ratings_test.txt')
|
◼ 데이터셋에 포함된 항목의 개수 출력
print(f'train_dataset : {len(train_dataset)}')
print(f'train_valid_dataset : {len(train_valid_dataset)}')
|
train_dataset : 50000 train_valid_dataset : 150000 |
◼ 데이터 확인
train_valid_dataset.head()
|
◼ 데이터프레임을 무작위로 섞기
train_valid_dataset = train_valid_dataset.sample(frac=1.)
train_valid_dataset.head()
|
|
|
◼ train_valid_dataset | 훈련 데이터셋(80%) 검증 데이터셋(20%)
train_ratio = 0.8 n_train = int(len(train_valid_dataset) * train_ratio)
train_df = train_valid_dataset[:n_train]
valid_df = train_valid_dataset[n_train:]
print(f'train samples : {len(train_df)}')
print(f'valid samples : {len(valid_df)}')
print(f'test samples: {len(valid_df)}')
|
train samples : 120000 valid samples : 30000 test samples: 30000 |
◼ 훈련 / 검증 / 테스트 데이터셋 | 10%씩 무작위로 샘플링
# 1/10으로 샘플링
train_df = train_df.sample(frac=0.1)
valid_df = valid_df.sample(frac=0.1)
test_df = test_df.sample(frac=0.1)
print(f'train samples : {len(train_df)}')
print(f'valid samples : {len(valid_df)}')
print(f'test samples: {len(test_df)}')
|
train samples : 12000 valid samples : 3000 test samples: 5000 |
◼ <클래스 : NSMCDataset > 훈련, 검증 및 테스트 데이터셋을 생성
class NSMCDataset(Dataset):
def __init__(self, data_df, tokenizer=None):
self.data_df = data_df
self.tokenizer = tokenizer
def __len__(self):
return len(self.data_df)
# doc, label, doc_ids,
def __getitem__(self, idx):
sample_raw = self.data_df.iloc[idx]
sample = {}
sample['doc'] = str(sample_raw['document'])
sample['label'] = int(sample_raw['label'])
if self.tokenizer is not None:
sample['doc_ids'] = self.tokenizer(sample['doc'])
return sample
train_dataset = NSMCDataset(data_df=train_df, tokenizer=tokenizer)
valid_dataset = NSMCDataset(data_df=valid_df, tokenizer=tokenizer)
test_dataset = NSMCDataset(data_df=test_df, tokenizer=tokenizer)
|
데이터셋 길이 반환
샘플 반환
|
◼ 데이터셋 생성(훈련/검증/테스트)
train_dataset = NSMCDataset(data_df=train_df, tokenizer=tokenizer)
valid_dataset = NSMCDataset(data_df=valid_df, tokenizer=tokenizer)
test_dataset = NSMCDataset(data_df=test_df, tokenizer=tokenizer)
|
|
◼ 문서 / 레이블 / 토큰화된 문서 ID (토크나이저가 주어진 경우) 출력
print(train_dataset[0])
|
{ 'doc': '기덕이의 쓰레기 시리즈', 'label': 0, 'doc_ids': [1, 47, 397, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] } |
이 코드는 train_dataset의 첫 번째 샘플을 출력합니다. train_dataset은 NSMCDataset 클래스의 인스턴스이므로, train_dataset[0]은 NSMCDataset 클래스의 __getitem__ 메서드를 호출하여 첫 번째 샘플을 반환합니다. __getitem__ 메서드의 동작
|
◼ < 함수: collate_fn > 여러 샘플을 하나의 배치(batch)로 병합
def collate_fn(batch):
keys = [key for key in batch[0].keys()]
data = {key: [] for key in keys}
for item in batch:
for key in keys:
data[key].append(item[key])
return data
|
◼ DataLoader | 훈련 / 검증 / 테스트 데이터셋을 배치로 로드
train_dataloader = DataLoader(
train_dataset,
batch_size=128,
collate_fn=collate_fn,
shuffle=True
)
valid_dataloader = DataLoader(
valid_dataset,
batch_size=128,
collate_fn=collate_fn,
shuffle=False
)
test_dataloader = DataLoader(
test_dataset,
batch_size=128,
collate_fn=collate_fn,
shuffle=False
)
|
|
◼ 문서 / 레이블 / 토큰화된 문서 ID (토크나이저가 주어진 경우) 출력
sample = next(iter(test_dataloader))
sample.keys() # dict_keys(['doc','label','doc_ids])
sample['doc'][3] # 정말 재미지게 오랫동안 보게되는 드라마
print(sample['doc_ids'][3]) # [4, 17366, 2223, 2798, 52, 0 ... 0 ]
|
|
|
3. CBOW(
- 자연어 처리에서 단어의 의미를 백터로 표현하는 Word2Vec 모델 중 하나
- 문맥(context) 단어들을 사용해서 타겟(target) 단어를 예측하는 것
1. 모델의 원리 |
|
◼ < 클래스 : CBOW (Continuous Bag of Words) >
주변 단어들을 사용하여 중심 단어를 예측하는 모델
class CBOW(nn.Module):
def __init__(self, vocab_size, embed_dim):
super().__init__()
self.output_dim = embed_dim
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
def forward(self, x):
# (batch_size, sequence) -> (batch_size, sequence, embed_dim)
x_embeded = self.embedding(x)
stnc_repr = torch.mean(x_embeded, dim=1) # batch_size * embed_dim
return stnc_repr
|
|
- 주석
import torch
import torch.nn as nn
class CBOW(nn.Module):
def __init__(self, vocab_size, embed_dim):
super().__init__()
# 임베딩 차원의 크기를 설정합니다.
self.output_dim = embed_dim
# 임베딩 층을 정의합니다. vocab_size는 어휘 사전의 크기, embed_dim은 임베딩 벡터의 차원입니다.
# padding_idx=0은 패딩 토큰을 나타내며, 임베딩 벡터는 0 벡터로 초기화됩니다.
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
def forward(self, x):
# 입력 x는 크기가 (batch_size, sequence)인 텐서입니다.
# Embedding 층을 통과하여 각 단어 인덱스를 임베딩 벡터로 변환합니다.
# 결과는 크기가 (batch_size, sequence, embed_dim)인 텐서입니다.
x_embedded = self.embedding(x)
# 각 문장에서 임베딩 벡터의 평균을 계산합니다. dim=1을 지정하여 sequence 차원에 대해 평균을 구합니다.
# 결과는 크기가 (batch_size, embed_dim)인 텐서입니다.
stnc_repr = torch.mean(x_embedded, dim=1)
# 문장의 임베딩 벡터를 반환합니다.
return stnc_repr
|
◼ <클래스: Classifier > 분류기 모델
주어진 단어 임베딩 모델(sr_model)을 기반으로 하여 분류 작업 수행
class Classifier(nn.Module):
def __init__(self, sr_model, output_dim, vocab_size, embed_dim, **kwargs):
super().__init__()
self.sr_model = sr_model(vocab_size=vocab_size, embed_dim=embed_dim, **kwargs)
self.input_dim = self.sr_model.output_dim
self.output_dim = output_dim
self.fc = nn.Linear(self.input_dim, self.output_dim)
def forward(self, x):
return self.fc(self.sr_model(x))
|
|
- 주석
import torch.nn as nn
class Classifier(nn.Module):
def __init__(self, sr_model, output_dim, vocab_size, embed_dim, **kwargs):
super().__init__()
# 주어진 sr_model로부터 단어 임베딩 모델을 생성합니다.
self.sr_model = sr_model(vocab_size=vocab_size, embed_dim=embed_dim, **kwargs)
# 입력 차원은 임베딩 모델의 출력 차원과 같습니다.
self.input_dim = self.sr_model.output_dim
# 출력 차원은 분류기에서 정의한 출력 차원입니다.
self.output_dim = output_dim
# Fully connected layer를 정의합니다. 입력은 임베딩 모델의 출력 차원, 출력은 분류기의 출력 차원입니다.
self.fc = nn.Linear(self.input_dim, self.output_dim)
def forward(self, x):
# 주어진 입력 데이터 x를 임베딩 모델에 통과시키고,
# 그 결과를 fully connected layer에 통과시켜 출력을 계산합니다.
return self.fc(self.sr_model(x))
|
◼ CBOW 모델을 사용하여 단어 임베딩을 구성하고, 이를 기반으로 분류를 수행
model= Classifier(sr_model=CBOW, output_dim=2, vocab_size=len(vocabs), embed_dim=16)
model.sr_model.embedding.weight[1]
|
tensor([-0.4840, 0.6809, -0.2001, 0.4840, -1.2526, 0.1653, -0.8083, -0.2570, 2.8789, -0.9947, -1.7143, -0.0661, 0.0123, 0.1575, -0.1187, -1.3056], grad_fn=<SelectBackward0>) |
|
|
- 다른예
◼ CUDA 사용 가능 여부를 확인, 가능한 경우 모델을 GPU로 이동
use_cuda = True and torch.cuda.is_available()
if use_cuda:
model.cuda()
|
|
|
◼ 학습에 필요한 최적화 함수와 손실 함수를 설정
Adam 최적화 알고리즘은 모델 파라미터를 업데이트,
CrossEntropyLoss는 모델의 출력과 실제 레이블 간의 손실을 계산, 이를 최소화하는 방향으로 학습이 진행
optimizer = optim.Adam(params=model.parameters(), lr=0.01)
calc_loss = nn.CrossEntropyLoss()
|
|
|
◼ 모델을 학습 / 검증 | 최적의 모델을 선택하는 과정
학습 중에는 훈련 데이터셋을 사용하여 모델을 업데이트,
검증 데이터셋을 사용하여 모델의 성능을 평가
n_epoch = 10
global_i = 0
valid_loss_history = []
train_loss_history = []
best_model = None
best_epoch_i = None
min_valid_loss = 9e+9
|
|
for epoch_i in range(n_epoch): model.train()
|
|
for batch in train_dataloader: optimizer.zero_grad()
X = torch.tensor(batch['doc_ids'])
y = torch.tensor(batch['label'])
if use_cuda:
X = X.cuda()
y = y.cuda()
y_pred = model(X)
loss = calc_loss(y_pred, y)
if global_i % 1000 == 0:
print(f'i: {global_i}, epoch: {epoch_i}, loss: {loss.item()}')
train_loss_history.append((global_i, loss.item()))
loss.backward()
optimizer.step()
global_i += 1
|
|
model.eval() valid_loss_list = []
for batch in valid_dataloader:
X = torch.tensor(batch['doc_ids'])
y = torch.tensor(batch['label'])
if use_cuda:
X = X.cuda()
y = y.cuda()
y_pred = model(X)
loss = calc_loss(y_pred, y)
valid_loss_list.append(loss.item())
valid_loss_mean = np.mean(valid_loss_list)
valid_loss_history.append((global_i, valid_loss_mean.item()))
|
|
if valid_loss_mean < min_valid_loss: min_valid_loss = valid_loss_mean
best_epoch_i = epoch_i
best_model = deepcopy(model)
|
|
if epoch_i % 2 == 0: print("*"*30)
print(f'valid_loss_mean: {valid_loss_mean}')
print("*"*30)
|
|
print(f'best_epoch: {best_epoch_i}') |
|
i: 0, epoch: 0, loss: 0.7254136800765991 ****************************** valid_loss_mean: 0.6611488809188207 ****************************** ****************************** valid_loss_mean: 0.5264493425687155 ****************************** ****************************** valid_loss_mean: 0.5448057514925798 ****************************** ****************************** valid_loss_mean: 0.5969234692553679 ****************************** ****************************** valid_loss_mean: 0.6635242116947969 ****************************** best_epoch: 2 |
- 전체코드
n_epoch = 10 # 총 에포크 수
global_i = 0 # 전체 반복 횟수
valid_loss_history = [] # 검증 손실 기록 리스트
train_loss_history = [] # 훈련 손실 기록 리스트
best_model = None # 최적의 모델을 저장할 변수
best_epoch_i = None # 최적의 에포크 인덱스를 저장할 변수
min_valid_loss = 9e+9 # 초기 최소 검증 손실 설정
# 에포크 반복
for epoch_i in range(n_epoch):
model.train() # 모델을 학습 모드로 설정
# 훈련 데이터셋 반복
for batch in train_dataloader:
optimizer.zero_grad() # optimizer의 gradient를 초기화
X = torch.tensor(batch['doc_ids']) # 입력 데이터
y = torch.tensor(batch['label']) # 레이블
if use_cuda:
X = X.cuda()
y = y.cuda()
y_pred = model(X) # 모델에 입력 데이터를 전달하여 예측값을 계산
loss = calc_loss(y_pred, y) # 손실 계산
# 매 1000번째 반복마다 손실 출력
if global_i % 1000 == 0:
print(f'i: {global_i}, epoch: {epoch_i}, loss: {loss.item()}')
train_loss_history.append((global_i, loss.item())) # 훈련 손실 기록
loss.backward() # 역전파를 통해 gradient 계산
optimizer.step() # optimizer를 사용하여 모델 파라미터 업데이트
global_i += 1 # 전체 반복 횟수 증가
model.eval() # 모델을 평가 모드로 설정
valid_loss_list = [] # 검증 손실을 저장할 리스트 초기화
# 검증 데이터셋 반복
for batch in valid_dataloader:
X = torch.tensor(batch['doc_ids']) # 입력 데이터
y = torch.tensor(batch['label']) # 레이블
if use_cuda:
X = X.cuda()
y = y.cuda()
y_pred = model(X) # 모델에 입력 데이터를 전달하여 예측값을 계산
loss = calc_loss(y_pred, y) # 손실 계산
valid_loss_list.append(loss.item()) # 검증 손실 기록
valid_loss_mean = np.mean(valid_loss_list) # 검증 손실의 평균 계산
valid_loss_history.append((global_i, valid_loss_mean.item())) # 검증 손실 기록
# 현재 검증 손실이 최소 검증 손실보다 작으면 최적 모델을 업데이트
if valid_loss_mean < min_valid_loss:
min_valid_loss = valid_loss_mean # 최소 검증 손실 업데이트
best_epoch_i = epoch_i # 최적의 에포크 인덱스 업데이트
best_model = deepcopy(model) # 최적의 모델을 deepcopy하여 저장
# 매 두 번째 에포크마다 현재 검증 손실을 출력
if epoch_i % 2 == 0:
print("*"*30)
print(f'valid_loss_mean: {valid_loss_mean}')
print("*"*30)
# 최적의 에포크 인덱스 출력
print(f'best_epoch: {best_epoch_i}')
|
◼ 훈련과 검증 손실의 추이를 시각화
def calc_moving_average(arr, win_size=100):
new_arr = []
win = []
for i, val in enumerate(arr):
win.append(val)
if len(win) > win_size:
win.pop(0)
new_arr.append(np.mean(win))
return np.array(new_arr)
valid_loss_history = np.array(valid_loss_history)
train_loss_history = np.array(train_loss_history)
plt.figure(figsize=(12,8))
plt.plot(train_loss_history[:,0],
calc_moving_average(train_loss_history[:,1]), color='blue')
plt.plot(valid_loss_history[:,0],
valid_loss_history[:,1], color='red')
plt.xlabel("step")
plt.ylabel("loss")
|
- 주석
def calc_moving_average(arr, win_size=100):
new_arr = [] # 이동 평균을 계산하여 저장할 리스트
win = [] # 이동 평균을 계산할 윈도우 리스트
for i, val in enumerate(arr):
win.append(val) # 현재 값을 윈도우에 추가
if len(win) > win_size:
win.pop(0) # 윈도우 크기를 유지하기 위해 가장 오래된 값 제거
new_arr.append(np.mean(win)) # 윈도우 내 값들의 평균을 계산하여 new_arr에 추가
return np.array(new_arr) # 계산된 이동 평균 배열 반환
valid_loss_history = np.array(valid_loss_history) # 검증 손실 기록을 NumPy 배열로 변환
train_loss_history = np.array(train_loss_history) # 훈련 손실 기록을 NumPy 배열로 변환
# 손실 추이를 시각화하는 그래프 생성
plt.figure(figsize=(12, 8)) # 그래프 크기 설정
plt.plot(train_loss_history[:, 0], calc_moving_average(train_loss_history[:, 1]), color='blue') # 훈련 손실의 이동 평균을 파란색으로 플롯
plt.plot(valid_loss_history[:, 0], valid_loss_history[:, 1], color='red') # 검증 손실을 빨간색으로 플롯
plt.xlabel("step") # x축 레이블 설정
plt.ylabel("loss") # y축 레이블 설정
|
◼ 테스트 데이터셋에서 정확도를 계산
model = best_model # 최적의 모델을 선택하여 model 변수에 할당
model.eval() # 모델을 평가 모드로 설정 (드롭아웃, 배치 정규화 등의 동작이 평가 모드로 변경됨)
total = 0 # 전체 예측 수 초기화
correct = 0 # 정확하게 예측된 수 초기화
# 테스트 데이터셋의 각 배치에 대해 반복하면서 예측 정확도 계산
for batch in tqdm(test_dataloader, total=len(test_dataloader.dataset)//test_dataloader.batch_size):
X = torch.tensor(batch['doc_ids']) # 입력 데이터
y = torch.tensor(batch['label']) # 실제 레이블
if use_cuda:
X = X.cuda()
y = y.cuda()
y_pred = model(X) # 모델을 사용하여 예측값 계산
# 예측값과 실제 레이블을 비교하여 정확한 예측 수 계산
curr_correct = y_pred.argmax(dim=1) == y
total += len(curr_correct) # 현재 배치의 전체 예측 수를 누적
correct += sum(curr_correct) # 현재 배치에서 정확하게 예측된 수를 누적
# 전체 테스트 데이터셋에 대한 정확도 계산 및 출력
print(f'test accuracy: {correct/total}')
|
0.7779250144958496 |
'AI > 자연어처리' 카테고리의 다른 글
11. LSTM과 GRU (0) | 2024.07.02 |
---|---|
10. CNN Text Classification (0) | 2024.07.02 |
08. RNN 기초 (0) | 2024.06.27 |
07. 워드 임베딩 시각화 (0) | 2024.06.27 |
06. 자연어처리 - 워드 임베딩 (0) | 2024.06.25 |