작성자 : 투빅스 13기 이재빈
Contents
- Matrix Gradients for Neural Nets
- Computation Graphs and Backpropagation
- Tips and Tricks for Neural Networks
Neural Network 과정은 다음과 같이 진행됩니다.
1. FeedForward : x vector 가 Weight Matrix 와 곱해져서, output vector를 만들어 냅니다.
2. BackPropagation : output vector 를 Weight matrix 에 대해 미분합니다. FeedForward 과정을 통해 나온 predict 값과 실제 값을 통해, error signal vector를 만들고, Chain Rule 을 이용해 가중치를 업데이트 합니다.
Backpropagation 과정에서 미분 계산 시, 도움이 되는 팁은 위와 같습니다.
softmax 미분 계산 시, correct class와 incorrect class를 구분하여 계산하라는 의미는 위와 같습니다.
i번째 class에 대한 softmax 출력값은 가 됩니다.
softmax 미분값인 는 일 때와 일 때 달라지므로 (노란색 말풍선), 이 점을 주의해 계산해야 합니다.
NLP task 에서는, window 내에 등장한 단어들이 input vector가 됩니다. input vector가 Neural Network의 FeedForward, Backpropagation 과정을 거쳐 가중치를 업데이트 하게 됩니다.
향후 수행할 Task (=우리가 풀고 싶은 자연어 처리의 구체적인 문제들)에 맞추어 가중치 업데이트를 진행하게 되는데, 이를 Downstream Task라고 합니다.
다운스트림 테스크의 대표적인 예시로는 품사 판별(POS, Part-Of-Speech Tagging), 개체명 인식(NER, Named Entity Recognition) 등이 있습니다.
강의에서는 데이터를 함부로 re-training 시키는 것은 좋지 않다고 말합니다.
pre-trained 된 데이터에서는 TV, telly, television 단어들이 모두 뭉쳐 있어서 비슷한 의미를 지니지만, re-training 시키게 되면 가지고 있는 데이터에 따라 이 세 단어의 임베딩 벡터가 달라질 수 있기 때문입니다.
따라서 NLP task는 대부분 pre-trained 되어 있는 모델을 가져와서, 주어진 목적에 맞게 fine-tuning 하는 방향으로 진행합니다.
1. pre-trained : 대규모 말뭉치 & 컴퓨팅 파워를 통해 embedding 값을 만듭니다. 해당 embedding 에는 말뭉치의 의미적, 문법적 맥락이 모두 포함되어 있습니다.
2. fine-tuning : 이후 embedding을 입력으로 하는 새로운 딥러닝 모델을 만듭니다. 우리가 풀고 싶은 구체적인 task와, 가지고 있는 소규모 데이터에 맞추어, embedding을 포함한 모델 전체를 update 합니다.
pre-trained : 학습된 모델을 불러옵니다.
pytorch code
# 적절한 BERT model 로딩
bert_base, vocabulary = nlp.model.get_model('bert_12_768_12',
dataset_name='wiki_multilingual_cased',
pretrained=True, ctx=ctx, use_pooler=True,
use_decoder=False, use_classifier=False)
# dataset을 불러오고, tokenizing 진행
ds = gluon.data.SimpleDataset([['나 보기가 역겨워', '김소월']])
tok = nlp.data.BERTTokenizer(vocab=vocabulary, lower=False)
trans = nlp.data.BERTSentenceTransform(tok, max_seq_length=10)
# BERT model의 input에 맞게 Data Preprocessing
class BERTDataset(Dataset):
def __init__(self, dataset, sent_idx, label_idx, bert_tokenizer, max_len,
pad, pair):
transform = nlp.data.BERTSentenceTransform(
bert_tokenizer, max_seq_length=max_len, pad=pad, pair=pair)
sent_dataset = gluon.data.SimpleDataset([[
i[sent_idx],
] for i in dataset])
self.sentences = sent_dataset.transform(transform)
self.labels = gluon.data.SimpleDataset(
[np.array(np.int32(i[label_idx])) for i in dataset])
def __getitem__(self, i):
return (self.sentences[i] + (self.labels[i], ))
def __len__(self):
return (len(self.labels))
fine-tuning : 기존의 학습된 layer에 데이터를 추가로 학습시켜서, parameter를 업데이트 해 task를 수행합니다.
# Classifier Task에 맞추어 Fine-Tuning
class BERTClassifier(nn.Block):
def __init__(self,
bert,
num_classes=2,
dropout=None,
prefix=None,
params=None):
super(BERTClassifier, self).__init__(prefix=prefix, params=params)
self.bert = bert
with self.name_scope():
self.classifier = nn.HybridSequential(prefix=prefix)
if dropout:
self.classifier.add(nn.Dropout(rate=dropout))
self.classifier.add(nn.Dense(units=num_classes))
def forward(self, inputs, token_types, valid_length=None):
_, pooler_out = self.bert(inputs, token_types, valid_length)
return self.classifier(pooler_out)
강의에서는,
1. 웬만하면 pre-trained word vector를 이용하도록 권하고 있습니다.
사람들이 방대한 데이터에 대해 학습을 시켜 놓은 데이터이기 때문에, 단어 간의 관계가 어느 정도 잘 형성되어 있기 때문입니다.
2. 그러나 100만개 이상의 데이터가 있다면, re-training이 성능에 도움이 될 것이라고 합니다.
가지고 있는 데이터에 특성에 따라 진행되는 fine-tuning 방법론은 위와 같습니다.
Backpropagation 과정에서, 주목해야 할 부분은
Local Gradient와 Downstream Gradient 입니다.
Downstream Gradient = Local Gradient * Upstream Gradient 이므로,
역전파를 분해하면 위와 같습니다.
주요 연산에 대한 분해 과정을 고려해 계산해 보면, Gradient는 다음과 같이 흐르게 됩니다.
Computation Graph는, '값을 그래프 노드 위에 위치시킨다'는 의미입니다.
tensorflow 1.0 사용 시에 설정하는 tf.placeholder 와 PyTorch에서 Dataset class에서 변수를 선언하는 것이 Computation Graph의 대표적인 예시입니다.
DeepLearning Framework들은 그래프 기반으로 연산을 진행하게 됩니다. 그래프에 위치시켜 연산을 진행하면 Forward 과정과 Backward 과정의 시간복잡도가 동일하게 되므로, 효율적으로 연산을 진행할 수 있습니다.
Numerical Gradient는 h에 아주 작은 값을 대입하여 순간 기울기를 구하는 것을 말하며, 이를 통해 Gradient를 잘 구했는지 체크할 수 있습니다.
Neural Network 과정에서는 Numerical Gradient 방법으로 연산을 진행하지 않는데, 계산 과정이 기하급수적으로 매우 많아져 비효율적으로 연산이 진행되기 때문입니다.
참고로, Numerical Gradient 방법으로 Neural Network를 진행하게 되면, Forward 과정에서는 , Backward 과정에서는 의 계산 비용이 소요됩니다.
Backpropagation을 그냥 진행하게 되면, 위와 같은 문제가 발생하게 됩니다.
따라서 강의에서는 이와 같은 문제를 방지하기 위한 여러가지 Neural Network Tip을 소개해 주고 있습니다.
Loss를 구할 때에는 LSE(Least Squared Error)가 최소가 되는 값, 즉 unbiased estimator를 찾는 것이 이상적입니다. 하지만 이를 위해서는 parameter가 증가할 수 밖에 없으므로, 모델이 복잡해지게 됩니다.
따라서 목적함수에 항을 하나 더 추가하여, 모델이 복잡해질수록 penalty를 부여합니다. 이를 통해, biased 하지만 smaller estimator 인 Loss값을 찾는 것이 regularization 입니다.
word vector를 개별적으로 실행하는 것 보다 한 개의 matrix를 만들어서 실행하는 것이 빠르며, 이 과정을 vectorization 이라 부릅니다.
activation function 은 ReLU를 가장 먼저 고려하는 것이 이상적인데, 간단하면서 성능이 괜찮기 때문입니다.
ReLU를 조금씩 변화시킨 activation function 등 다양한 활성함수가 등장하고 있는 추세입니다.
학습 전 가중치를 초기화하여, Gradient Vanishing 이나 Gradient Exploding 현상을 방지합니다.
적절한 Optimizer를 사용하여 최적 값을 찾아갑니다.
학습시킬 때 적절한 learning rate를 설정하는 것이 중요합니다.
Learning Rate Scheduling 을 통해 상황에 따라 변환시키면서 학습을 진행하기도 합니다.
# Learning Rate Scheduling
ReduceLROnPlateau(monitor='loss', mode="auto", factor=0.25, min_delta=0.0001, cooldown=0, min_lr=0.00001)
# Cyclic Learning Rate
cyclic_learning_rate( global_step, # Epoch
learning_rate=0.01,
max_lr=0.1, # 최대 learning rate
step_size=20.,
gamma=0.99994, # 작게 할수록 진폭이 더 크게 변한다
mode='triangular',
name=None):