일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 자연어처리
- 파이토치
- MSE
- 홍콩과기대김성훈교수
- 강의정리
- pytorch
- 백준
- BAEKJOON
- 딥러닝
- 알고리즘
- AI
- DP
- 강의자료
- machine learning
- 파이썬
- rnn
- loss
- Deep learning
- 머신러닝 기초
- DynamicProgramming
- Python
- 머신러닝
- Cross entropy
- tensorflow
- 스택
- Natural Language Processing with PyTorch
- classifier
- 정렬
- Softmax
- Hypothesis
- Today
- Total
개발자의시작
Natural Language Processing with PyTorch 정리 3-4 본문
이 글은 Natural Language Processing with Pytorch 강의자료를 번역 및 정리해놓은 글입니다.
강의 자료 및 코드 링크 :
A Perceptron Classifier
이 예에서 우리가 사용하는 모델은 우리가 장 초반에 보여준 Perceptron 분류기의 재구현이다. ReviewClassifier는 PyTorch의 모듈로부터 상속받고 단일 출력으로 단일 선형 레이어를 생성한다. 이것은 이항 분류 설정(부정 또는 긍정 리뷰)이기 때문에 적절한 설정이다. 시그모이드 함수는 최종 비선형성으로 사용된다.
시그모이드 함수를 선택적으로 적용할 수 있도록 forward() 메서드를 매개변수화 한다. 그 이유를 이해하려면 먼저 이항 분류 과제에서 이항 교차 엔트로피 손실(torch.nn.BCELoss)을 지적하는 것이 가장 중요하다. BCELoss()는 가장 적절한 손실 함수이다. 이항 확률에 대해 수학적으로 공식화되었다. 단, 시그모이드 함수를 적용한 다음 이 손실 함수를 사용하는 경우 수치 안정성 문제가 있다. PyTorch는 사용자에게 보다 안정적인 바로가기를 제공하기 위해 BCEWithLogitsLoss()를 제공한다. 이 손실 함수를 사용하려면 출력에 시그모이드 함수가 적용되지 않아야 한다. 따라서 기본적으로 우리는 시그모이드 함수를 사용하지 않는다. 그러나 분류기의 사용자가 확률 값을 원하는 경우에는 시그모이드 함수가 필요하며, 옵션으로 남겨진다. 이러한 방식으로 사용되는 예를 다음 결과 섹션 example3-18에서 볼 수 있다.
example 3-18. A perceptron classifier for classifying Yelp reviews
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import torch.nn as nn
import torch.nn.functional as F
class ReviewClassifier(nn.Module):
""" a simple perceptronbased classifier """
def __init__(self, num_features):
"""
Args:
num_features (int): the size of the input feature vector
"""
super(ReviewClassifier, self).__init__()
self.fc1 = nn.Linear(in_features=num_features,out_features=1)
def forward(self, x_in, apply_sigmoid=False):
"""The forward pass of the classifier
Args:
x_in (torch.Tensor): an input data tensor
x_in.shape should be (batch, num_features)
apply_sigmoid (bool): a flag for the sigmoid activation
should be false if used with the crossentropylosses
Returns:
the resulting tensor. tensor.shape should be (batch,).
"""
y_out = self.fc1(x_in).squeeze()
if apply_sigmoid:
y_out = F.sigmoid(y_out)
return y_out
|
The Training Routine
이 섹션에서는 학습 루틴의 구성요소와 이들이 데이터셋 및 모델과 어떻게 결합하여 모델 매개변수를 조정하고 성능을 높이는지 소개한다. 그 중심에서, 학습 루틴은 모델을 인스턴스 화하고, 데이터셋을 반복하며, 데이터를 입력했을 때 모델의 출력을 계산하고, 손실을 계산하고(모델이 얼마나 잘못되었는가), 손실에 비례한 모델을 업데이트하는 일을 담당한다. 관리해야 할 내용이 많아 보일 수 도 있지만 학습 과정을 바꿀 곳이 많지 않아 딥러닝 개발 과정에서 습관화될 것이다. 보다 높은 수준의 의사 결정 관리를 지원하기 위해 우리는 다음 사항을 사용한다. args는 모든 결정 지점을 중앙에서 조정하는 것을 목표로 하며 example3-19에서 볼 수 있다.
Example 3-19. Hyperparameters and program options for the perceptronbased Yelp review classifier
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from argparse import Namespace
args = Namespace(
# Data and path information
frequency_cutoff=25,
model_state_file='model.pth',
review_csv='data/yelp/reviews_with_splits_lite.csv',
save_dir='model_storage/ch3/yelp/',
vectorizer_file='vectorizer.json',
# No model hyperparameters
# Training hyperparameters
batch_size=128,
early_stopping_criteria=5,
learning_rate=0.001,
num_epochs=100,
seed=1337,
# Runtime options omitted for space
)
|
이 섹션의 나머지 부분에서는 먼저 교육 과정에 대한 정보를 추적하기 위해 사용하는 작은 사전인 교육 상태를 설명한다. 이 사전은 당신이 학습 루틴에 대한 더 많은 세부사항을 추적함에 따라 성장할 것이고, 그렇게 하도록 선택한다면 당신은 그것을 체계화할 수 있을 것이다. 그러나 우리의 다음 예에 제시된 사전은 당신이 모델 훈련 동안 추적할 기본적인 정보의 집합이다. 학습 상태를 설명한 후, 모델 모델 교육을 위해 인스턴스화 되는 일련의 오브젝트를 개략적으로 설명한다. 여기에는 모델 자체, 데이터 집합, 최적화 도구 및 손실 함수가 포함된다. ㅇ다른 예시와 보충 자료에는 추가 구성요소를 포함하지만 단순성을 위해 텍스트에 나열하지는 않는다. 마지막으로 본 섹션을 학습 루프 자체로 마무리하고 표준 PyTorch 최적화 패턴을 시연한다.
SETTING THE STAGE FOR THE TRAINING TO BEGIN
example.3-20은 우리가 이 예를 위해 인스턴스화 하는 학습 구성요소를 보여준다. 첫 번째 항목은 초기 훈련 상태이다. 이 함수는 학습 상태가 복잡한 정보를 처리할 수 있도록 args 객체를 인수로 허용하지만 이 책의 텍스트에서는 이러한 복잡성을 표시하지 않는다. 학습 상태에서 사용할 수 있는 추가 사항을 확인하려면 보충 자료를 참조하십시오. 여기에 표시된 최소 세트에는 epoch index와 학습 손실(training loss), 학습 정확도(training accuracy), 유효성 검사 손실(validation loss), 유효성 검사 정확도(validation accuracy) 목록이 포함된다. 또한 테스트 손실 및 테스트 정확도에 대한 두 가지 필드가 포함된다.
다음 두 가지 항목은 데이터 집합과 모델이다. 이 예와 책의 나머지 예에서 우리는 vectorizer를 인스턴스화 하는 것을 책임지는 데이터셋을 설계한다. 보조 자료에서 데이터 집합 인스턴스화는 이전에 인스턴스화 된 벡터 라이저를 로드하거나 벡터라이저를 디스크에 저장하는 새로운 인스턴스화를 허용하는 if 문에 중첩된다. args.cuda를 통해 사용자의 희망과 GPU 장치가 실제로 사용 가능한지 여부를 확인하는 조건에 따라 모델이 올바른 장치로 이동하는 것이 중요하다. 대상 장치는 코어 학습 루프의 generate_batch() 함수 호출에 사용되므로 데이터와 모델이 동일한 장치 위치에 있게 된다.
초기 인스턴스화의 마지막 두 가지 항목은 손실 함수와 최적화 도구이다. 이 예에서 사용되는 손실 함수는 BCEWithLogitsLoss()이다. ( " A Perceptron Classier"에서 언급한 바와 같아. 이항 분류를 위한 가장 적절한 손실 함수는 이항 교차 엔트로피 손실이며, BCELoss 함수에 시그모이드 함수를 적용하는 것보다 출력에 시그모이드 함수를 적용하지 않는 모델과 결합하는 것이 수치적으로 더 안정적이다. ) 우리가 사용하는 최적화 프로그램은 Adam optimizer이다. Adam은 다른 옵티마이저와 경쟁이 치열하며, 이 글을 쓰는 시점에서 Adam보다 다른 옵티마이저를 사용한 강력한 증거는 없다. 다른 옵티마이저를 시도하고 성능을 확인하여 이를 직접 확인하는 것도 좋다.
Example 3-20. Instantiating the dataset, model, loss, optimizer, and training state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import torch.optim as optim
def make_train_state(args):
return {'epoch_index': 0,
'train_loss': [],
'train_acc': [],
'val_loss': [],
'val_acc': [],
'test_loss': 1,
'test_acc': 1}
train_state = make_train_state(args)
if not torch.cuda.is_available():
args.cuda = False
args.device = torch.device("cuda" if args.cuda else "cpu")
# dataset and vectorizer
dataset = ReviewDataset.load_dataset_and_make_vectorizer(args.review_csv)
vectorizer = dataset.get_vectorizer()
# model
classifier = ReviewClassifier(num_features=len(vectorizer.review_vocab))
classifier = classifier.to(args.device)
# loss and optimizer
loss_func = nn.BCEWithLogitsLoss()
optimizer = optim.Adam(classifier.parameters(), lr=args.learning_rate)
|
THE TRAINING LOOP
학습 루프는 초기 인스턴스화로부터의 객체를 사용하며 모델 매개변수를 업데이트하여 시간이 지남에 따라 개선되도록 한다. 좀 더 구체적으로 말하면, 훈련 루프는 데이터 집합의 미니배치 위의 내부 루프와 여러 번 내부 루프를 반복하는 외부 루프라는 두 개의 루프로 구성되어 있다. 내부 루프에서는 각 미니배치에 대해 손실이 계산되며, 옵티마이저는 모델 파라미터를 업데이트하는 데 사용된다. example3-21은 코드에 진행상황을 보다 철저히 보여준다.
Example 3-21. A barebones training loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
for epoch_index in range(args.num_epochs):
train_state['epoch_index'] = epoch_index
# Iterate over training dataset
# setup: batch generator, set loss and acc to 0, set train mode on
dataset.set_split('train')
batch_generator = generate_batches(dataset,
batch_size=args.batch_size,
device=args.device)
running_loss = 0.0
running_acc = 0.0
classifier.train()
for batch_index, batch_dict in enumerate(batch_generator):
# the training routine is 5 steps:
# step 1. zero the gradients
optimizer.zero_grad()
# step 2. compute the output
y_pred = classifier(x_in=batch_dict['x_data'].float())
# step 3. compute the loss
loss = loss_func(y_pred, batch_dict['y_target'].float())
loss_batch = loss.item()
running_loss += (loss_batch running_loss) / (batch_index + 1)
# step 4. use loss to produce gradients
loss.backward()
# step 5. use optimizer to take gradient step
optimizer.step()
# #compute the accuracy
acc_batch = compute_accuracy(y_pred, batch_dict['y_target'])
running_acc += (acc_batch running_acc) / (batch_index + 1)
train_state['train_loss'].append(running_loss)
train_state['train_acc'].append(running_acc)
# Iterate over val dataset
# setup: batch generator, set loss and acc to 0, set eval mode on
dataset.set_split('val')
batch_generator = generate_batches(dataset,
batch_size=args.batch_size,
device=args.device)
running_loss = 0.
running_acc = 0.
classifier.eval()
for batch_index, batch_dict in enumerate(batch_generator):
# step 1. compute the output
y_pred = classifier(x_in=batch_dict['x_data'].float())
# step 2. compute the loss
loss = loss_func(y_pred, batch_dict['y_target'].float())
loss_batch = loss.item()
running_loss += (loss_batch running_loss) / (batch_index + 1)
# step 3. compute the accuracy
acc_batch = compute_accuracy(y_pred, batch_dict['y_target'])
running_acc += (acc_batch running_acc) / (batch_index + 1)
train_state['val_loss'].append(running_loss)
train_state['val_acc'].append(running_acc)
|
첫 번째 줄에서는 epoch을 수행할 for 루프를 사용한다. epoch의 수는 하이퍼 파라미터로 설정할 수 있다. 학습 루틴을 수행해야 하는 데이터 세트를 통과하는 횟수를 제어한다. 실제로, 이 루프가 종료되기 전에 종료하기 위해 조기 중지(early stopping) 기준과 같은 것을 사용해야 한다. 보충 자료에서 어떻게 할 수 있는지 보여준다.
for 루프 상단에서 몇 가지 일반적 정의와 인스턴스화가 일어난다. 첫째, 훈련 epoch index를 설정한다. 그런 다음 데이터 집합의 분할을 설정한다(처음에는 "train"으로, 나중에 epoch 끝에서 모델 성능을 측정하고자 할 때는 'val'로, 마지막으로 모델의 최종 성능을 평가하고자 할 때는 "test"로 설정) 데이터 집합을 구성하는 방법을 고려할 때 generate_batch()를 호출하기 전에 항상 분할을 설정해야 한다. batch_generator가 생성된 후, 두 개의 float이 batch에서 batch로 손실과 정확성을 추적하기 위해 인스턴스화 된다. 마지막으로, 우리는 claassifier의 trian()메서드를 불러 모델이 "학습 모드"에 있고 모델 파라미터가 변이 가능하다는 것을 표시한다. 이것은 또한 드롭아웃과 같은 정규화 메커니즘을 가능하게 한다.
학습 루프의 다음 부분은 batch_generator의 학습 배치를 반복하며 모델 매개변수를 업데이트하는 필수 연산을 수행한다. 각 배치 반복 안에서 옵티마이저의 그래디언트는 먼저 optimizer.zero_grad()를 사용하여 재설정된다. 그런 다음 모델에서 출력을 계산한다. 다음으로, 손실 함수는 모델 출력과 학습 대상(true class labels) 사이의 손실을 계산하는 데 사용된다. 이후 loss.backward()방법을 loss object(손실 함수 객체가 아닌)에서 호출하여 그래디언트가 각 파라미터로 전파된다. 마지막으로 옵티마이저는 이러한 전파된 그래디언트를 사용하여 optimizer.step() 방법을 사용하여 매개변수 업데이트를 수행한다. 이 다섯 단계는 경사 하강을 위한 필수 단계다. 이를 넘어 기록과 추적을 위한 두어 번의 추가 작업이 있다. 특히 손실 및 정확도 값을 계산한 다음 running loss 및 running accuracy 변수를 업데이트하는 데 사용한다.
분할된 학습 배치의 내부 루프 후, 기록 및 인스턴스화 작업이 몇 가지 더 있다. 학습 상태는 먼저 최종 손실 및 정확도 값으로 업데이트된다. 그런 다음 새로운 배치 generator, running loss, running accuracy가 생성된다. 검증 데이터의 루프는 훈련 데이터와 거의 동일하므로 동일한 변수가 재사용된다. 그러나 큰 차이가 있다. 분류기의 .eval() 메서드를 호출하여 분류기의 .train() 메서드에 역연산을 수행한다. .eval() 메서드는 모델 파라미터를 불변하게 한다. 평가 모드에서는 또한 손실 계산과 파라미터로 돌아가는 그래디언트의 전파를 비활성화한다. 모델이 검증 데이터에 상대적인 파라미터를 조정하기를 원하지 않기 때문에 이것은 중요하다. 대신, 우리는 이 데이터가 모델이 얼마나 잘 수행되고 있는지를 나타내는 척도가 되기를 바란다. 학습 데이터에 대한 측정된 성과와 검증 데이터에 대한 측정된 성과 사이에 큰 차이가 있는 경우 모델이 학습 데이터에 지나치게 적합할 가능성이 높으며, 모델 또는 학습 루틴을 조정해야 한다.
유효성 검사 데이터를 반복하고 결과 유효성 검사 손실 및 정확도 값을 저장한 후, 외부 루프(outer for loop)가 완료된다. 우리가 이 책에서 시행하는 모든 훈련 일과는 매우 유사한 디자인 패턴을 따를 것이다. 사실 모든 경사 하강 알고리즘은 유사한 설계 패턴을 따른다. 당신이 이 루프를 쓰는 것에 익숙해진 후에, 당신은 경사 하강을 수행하는 것이 무엇을 의미하는지 배울 것이다.
Evaluation, Inference, and Inspection
훈련된 모델을 확보한 후 다음 단계는 데이터의 일부 보류된 부분에 대해 어떻게 했는지 평가하거나, 새로운 데이터에 대한 추론을 위해 모델을 사용하거나, 모델 가중치를 검사하여 학습한 내용을 확인하는 것이다. 이 섹션에서는 세 단계를 모두 보여준다.
EVALUATING ON TEST DATA
테스트 데이터를 평가하기 위해 코드는 이전 예에 보았던 유효성 검사 루프와 완전히 동일하지만, 한 가지 사소한 차이가 있다. 즉, 분할은 'val'이 아닌 'test'로 설정된다. 데이터 집합의 두 파티션 간의 차이는 테스트 세트가 가능한 한 적게 실행되어야 한다는 사실에서 비롯된다. 테스트 세트에서 훈련된 모델을 실행하고, 새로운 모델 결정을 내리고, 테스트 세트에서 재교육된 새 모델을 재측정할 때마다, 당신은 테스트 데이터에 대한 모델링 결정을 편중시키고 있다. 다시 말해, 그 과정을 충분히 자주 반복한다면, 테스트 세트는 정확한 척도로 무의미해진다. example3-22는 이것을 좀 더 면밀하게 살펴본다.
Example 3-22.Test set evaluation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
dataset.set_split('test')
batch_generator = generate_batches(dataset,
batch_size=args.batch_size,
device=args.device)
running_loss = 0.
running_acc = 0.
classifier.eval()
for batch_index, batch_dict in enumerate(batch_generator):
# compute the output
y_pred = classifier(x_in=batch_dict['x_data'].float())
# compute the loss
loss = loss_func(y_pred, batch_dict['y_target'].float())
loss_batch = loss.item()
running_loss += (loss_batch running_loss) / (batch_index + 1)
# compute the accuracy
acc_batch = compute_accuracy(y_pred, batch_dict['y_target'])
running_acc += (acc_batch running_acc) / (batch_index + 1)
train_state['test_loss'] = running_loss
train_state['test_acc'] = running_acc
print("Test loss: {:.3f}".format(train_state['test_loss']))
print("Test Accuracy: {:.2f}".format(train_state['test_acc']))
|
INFERENCE AND CLASSIFYING NEW DATA POINTS
모델을 평가하는 또 다른 방법은 새로운 데이터에 대한 추론을 하고 모델이 작동하는지 여부에 대한 질적 판단을 하는 것이다. 우리는 이것을 example3-23에서 볼 수 있다.
Example 3-23. Printing the prediction for a sample review
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
def predict_rating(review, classifier, vectorizer,decision_threshold=0.5):
"""Predict the rating of a review
Args:
review (str): the text of the review
classifier (ReviewClassifier): the trained model
vectorizer (ReviewVectorizer): the corresponding vectorizer
decision_threshold (float): The numerical boundary which
separates the rating classes
"""
review = preprocess_text(review)
vectorized_review = torch.tensor(vectorizer.vectorize(review))
result = classifier(vectorized_review.view(1, 1))
probability_value = F.sigmoid(result).item()
index = 1
if probability_value < decision_threshold:
index = 0
return vectorizer.rating_vocab.lookup_index(index)
test_review = "this is a pretty awesome book"
prediction = predict_rating(test_review, classifier, vectorizer)
print("{} -> {}".format(test_review, prediction)
|
INSPECTING MODEL WEIGHTS
마지막으로, 학습을 마친 후 모델이 잘 학습되었는지를 이해하는 마지막 방법은 weight를 검사하고 그것이 맞는 것처럼 보이는지 질적으로 판단하는 것이다. example3-24가 보여주듯이, 각 모델의 weight는 우리의 어휘에 있는 단어와 정확히 일치하기 때문에, 퍼셉트론과 붕괸된 원핫 인코딩을 사용하면 이것은 꽤 간단하다.
1
2
3
4
5
6
7
8
9
10
|
# Sort weights
fc1_weights = classifier.fc1.weight.detach()[0]
_, indices = torch.sort(fc1_weights, dim=0, descending=True)
indices = indices.numpy().tolist()
# Top 20 words
print("Influential words in Positive Reviews:")
print("")
for i in range(20):
print(vectorizer.review_vocab.lookup_index(indices[i]))
|
1
2
3
4
5
6
|
# Top 20 negative words
print("Influential words in Negative Reviews:")
print("")
indices.reverse()
for i in range(20):
print(vectorizer.review_vocab.lookup_index(indices[i]))
|
이상입니다.
'자연어처리' 카테고리의 다른 글
Natural Language Processing with PyTorch 정리 3-3 (0) | 2020.06.10 |
---|---|
Natural Language Processing with PyTorch 정리 3-2 (0) | 2020.05.13 |
Natural Language Processing with PyTorch 정리 3-1 (0) | 2020.05.12 |
[정보검색] 역색인파일(inverted index file) 정리 (0) | 2020.04.14 |
Python 한국어 맞춤법 검사기 py-hanspell 라이브러리 사용법 (7) | 2020.04.07 |