Text Classification이란, 텍스트∙문장∙문서를 입력으로 받아 사전에 정의된 클래스중 어디에 속하는지 분류(classification)하는 과정으로 아래와 같이 응용분야가 다양하다.
문제
클래스 예시
감성분석 (Sentiment Analysis)
긍정 / 중립 / 부정
스팸메일 탐지 (Spam Detection)
정상 / 스팸
사용자 의도 분류 (Intent Classification)
명령 / 질문 / 잡담 등
주제 분류 (Topic Classification)
각 주제
카테고리 분류 (Category Classification)
각 카테코리
딥러닝 이전에는 Naïve Bayes Classification, Support Vector Machine 등 다양한 방법으로 Text Classification을 진행하였다. 이번시간에는 딥러닝 이전의 가장 간단한 분류방식인 Naïve Bayes방식을 비롯, 여러 딥러닝 방식을 알아보자.
2. Naïve Bayes 활용하기
Naïve Bayes는 아주 강력한("각 feature는 independent하다!"라는 강력한 가정을 가짐) 분류방식으로
성능은 준수하지만 단어라는 불연속적인 symbol을 다루는 NLP에서는 아쉬운 면이 존재한다.
2.1 MAP (Maximum A Posterior)
❗️Bayes Theorem 이때, 대부분의 문제에서 evidence, P(D)는 구하기 어렵기에 P(c | D) ∝ P(D | c)∙P(c) 식으로 접근하기도 한다. 앞의 성질을 이용하면, 주어진 data D에 대해 확률을 최대로 하는 클래스 c를 구할 수 있는데,
❗️MAP 이처럼 사후확률을 최대화하는 클래스 c를 구하는 것을 MAP(사후확률최대화)라 한다.
❗️MLE 이와 마찬가지로 가능도를 최대화하는 클래스 c를 구하는 것을 MLE(최대가능도추정)이라 한다.
MLE는 주어진 data D와 label C에 대해 확률분포를 근사하기 위한 parameter θ를 훈련하기위한 방법으로 사용된다.
MLE. vs. MAP MAP가 경우에 따라 MLE보다 더 정확할 수 있다. (∵ 사전확률이 포함되어있어서)
2.2 Naïve Bayes Naïve Bayes는 MAP를 기반으로 작동한다. 가정: 각 feature는 independent하다! 라는 강력한 가정을 바탕으로 진행된다. 대부분의 경우, 사후확률을 구하기 어렵기에 가능도와 사전확률의 곱으로 클래스를 예측한다.
만약 다양한 특징으로 이루어진 data의 경우, feature가 희박하기에 가능도를 구하기 또한 어렵다. 이때, Naïve Bayes가 매우 강력한 힘을 발휘하는데, 각 특징이 독립적이라는 가정을 통해 사전확률이 실제 data corpus에서 출현한 빈도를 통해 추정이 가능해지는 것이다.
이처럼 간단한 가정으로 데이터의 희소성문제를 해결하는 쉽고 강력한 방법으로 MAP의 정답클래스라벨예측이 가능해지는 것이다.
상세예시 및 식은 아래 2.3을 참고
2.3 Sentiment Analysis 예제
위와 같이 class와 data가 긍정/부정과 document로 주어질 때, 'I am happy to see this movie' 라는 문장이 주어진다면, 이 문장이 긍정인지 부정인지 판단해보자! Naïve Bayes를 활용해 단어의 조합에 대한 확률을 각각 분해할 수 있다. 즉, 각 단어의 출현확률을 독립적이라 가정 후, 결합가능도확률을 모두 각각의 가능도확률로 분해한다. 그렇게 되면 데이터 D에서의 출현빈도를 구할 수 있다.
이처럼 corpus에서 단순히 각 단어의 class당 출현빈도를 계산하는 것만으로도 간단한 sentiment analysis가 가능하다.
2.4Add-One Smoothing Naïve Bayes가정을 통해 corpus에서 출현확률을 독립으로 만들어 출현횟수를 적극적으로 활용할 수 있게 되었다. 여기서 문제점이 발생하는데, 만약 Count(happy, neg)=0이라면? P(happy | neg)=0이 되어버린다.
아무리 data corpus에 존재하지 않더라도 그런 이유로 해당 sample의 출현확률을 0으로 추정해버리는 것은 매우 위험한 일이 되어버리기에 아래처럼 분자(출현횟수)에 1을 더해주면 쉽게 문제해결이 가능하다.(물론 완벽한 해결법은 아님)
2.5 장점 및 한계 장점: 단순히 출현빈도를 세는 것처럼 쉽고 간단하지만 강력!! 딥러닝을 활용하기에 label랑 문장 수가 매우 적은 경우, 오히려 복잡한 딥러닝방식보다 더 나은 대안이 될 수 있다.
한계: 'I am not happy'와 'I am happy'에서 not의 추가로 문장은 정반대뜻이 된다. 수식으로는 P(not, happy) ≠ P(not)∙P(happy)가 된다. 단어간 순서로 인해 생기는 정보도 무시할 수 없는데, "각 특징은 서로 독립적이다."라는 Naïve Bayes의 기본가정은 언어의 이런 특징을 단순화해 접근해 한계가 존재한다.
3. 흔한 오해 2
표제어추출(lemmatization), 어간추출(stemming)을 수행해 접사등을 제거한 이후 Text Classification을 진행해야하는가?? 예를들어, "나는 학교에 가요"라는 원문이 있다면, [나 학교 가] 처럼 어간추출이 진행된다.
이는 적은 corpus에서 효과를 발휘하여 희소성문제에서 어느정도의 타협점이 존재할 수 있게된다. 특히, DNN이전 전통적 기계학습방법에서 불연속적 존재인 자연어에 좋은 돌파구를 마련해주었다.
하지만, DNN시대에서는 성공적으로 차원축소(https://chan4im.tistory.com/197#n2)를 수행할 수 있게 되면서 희소성문제는 더이상 큰 장애물이 되지는 않기에 lemmazation, stemming등은 반드시 정석이라 하긴 어렵다.
또한, "나는 학교에 가요" / "나만 학교에 가요" 라는 두 문장은 서로 긍정 / 부정이라는 다른 class를 갖기에 lemmazation이나 stemming을 한 후 Text Classification에 접근하는 것은 바람직하지 못한 방법일 수도 있다. 따라서 이후 설명될 신경망모델을 사용해 text classification을 시도하는것이 훨씬 바람직하다.
만약, 성능향상을 위해 tuning 및 여러 시도에서 corpus의 부족이 성능저하의 원인이라 생각될 때, 추가적인 실험으로는 괜찮은 시도가 될 수 있다.
4. RNN 활용하기
이제 DNN을 통한 text classification문제를 살펴보자. 가장 간단한 방법은 RNN을 활용하는 것으로 sequential data라는 문장의 특징을 가장 잘 활용가능한 신경망 구조이다.
n개의 단어로 이루어진 문장 x에 대해 RNN이 순전파 시, n개의 hidden_state를 얻는다. 이때, 가장 마지막 은닉층으로 text classification이 가능하며 RNN은 입력으로 주어진 문장을 분류문제에 맞게 encoding할 수 있다. 즉, RNN의 출력값은 문장임베딩벡터(sentence embedding vector)라 할 수 있다.
4.1 Architecture 알다시피, text에서 단어는 불연속적 값이기에 이들이 모인 문장 또한, 불연속적값이다. 즉, 이산확률분포에서 문장을 sampling한 것이므로 입력으로는 one-hot벡터들이 여러 time-step으로 주어진다.
mini-batch까지 고려한다면, 입력은 3차원 tensor (n×m×|V|)가 될 것이다. ∙ n : mini_batch size (= 한번에 처리할 문서의 개수) ∙ m : sentence length (= feature vector의 차원수 = 텍스트 문서의 단어의 개수) ∙|V| : Vocabulary size (= Dataset내의 고유한 단어/토큰의 총 수)
하지만 원핫벡터는 주어진 |V| 차원에 단 하나의 1과 |V|-1개의 0으로 이루어진다. 효율적 저장을 위해 굳이 원핫벡터 전체를 가지고 있을 필요는 없기에 원핫벡터를 0 ~ |V|-1 사이 정수로 나타낼 수 있게 된다면, 2차원 matrix (n×m)으로 충분히 나타낼 수 있다.
이렇게 원핫인코딩된 (n×m) tensor를 embedding층에 통과시키면, word embedding tensor를 얻을 수 있다.
이후 word_embedding tensor를 RNN에 통과시키면 된다. 이때, 우린 RNN에 대해 각 time-step별, 계층별로 구분해 word_embedding tensor나 hidden_state를 넣어줄 필요가 없다.
최종적으로 제일 마지막 time-step만 선택해 softmax층을 통과시켜 이산확률분포 P(y | x;θ)로 나타낼 수 있다. 이때 제일 마지막 time-step은 H[:, -1]과 같은 방식으로 index slicing을 통해 도출할 수 있다.
모델구조로 보면 아래와 같다.
마지막으로 원핫벡터 y이기에 인덱스의 로그확률값만 최대화하면 되므로 CE Loss 수식은 NLL(음의 로그가능도)를 최소화하는 것과 동치이다.
Pytorch 구현예제 앞의 수식을 pytorch로 구현한 예제코드로 여러계층으로 이뤄진 LSTM을 사용했다. ∙ LSTM에는 각 층마다 Dropout이 사용되며 ∙ NLL(음의 로그가능도)손실함수로 최적화하기 위해 logsoftmax로 로그확률을 반환한다.
5.3 Text Classification with CNN CNN은 RNN과 달리 순차적 정보보다는 패턴인식 및 파악에 중점을 두는 구조를 갖는다. CNN은 classification에 중요한 단어들의 조합에 대한 패턴을 감지하기도 하는데, 해당 클래스를 나타내는 단어조합에 대한 pattern의 유무를 가장 중시한다.
예를들어, 'good'이라는 단어는 긍정/부정 분류에 핵심이 되는 중요한 signal로 작동한다. 그렇다면, 'good'에 해당하는 embedding vector의 pattern을 감지하는 filter를 모델이 학습한다면? → 'better', 'best', 'great'등의 단어들도 'good'과 비슷한 벡터값을 갖게 될 것이다. → 더 나아가 단어들의 조합 패턴(word sequence pattern)을 감지하는 filter도 학습이 가능할 것이다.
모델 구조에 대해 간단히 설명하자면, 아래와 같다. 먼저 one-hot벡터를 표현하는 인덱스값을 단어임베딩벡터(1차원)로 변환한다. 그 후 문장 내 모든 time-step의 단어임베딩벡터를 합치면 2차원 행렬이 된다. 그 후 Convolution Operation을 수행하면 CNN이 효과를 발휘한다.
Pytorch 구현예제 RNN의 text classification처럼 NLL(음의 로그가능도)손실함수로 최적화하기 위해 logsoftmax로 로그확률을 반환한다.
import torch
import torch.nn as nn
class CNNClassifier(nn.Module):
def __init__(
self,
input_size,
word_vec_size,
n_classes,
use_batch_norm=False,
dropout_p=.5,
window_sizes=[3, 4, 5],
n_filters=[100, 100, 100],
):
self.input_size = input_size # vocabulary size
self.word_vec_size = word_vec_size
self.n_classes = n_classes
self.use_batch_norm = use_batch_norm
self.dropout_p = dropout_p
# window_size means that how many words a pattern covers.
self.window_sizes = window_sizes
# n_filters means that how many patterns to cover.
self.n_filters = n_filters
super().__init__()
self.emb = nn.Embedding(input_size, word_vec_size)
# Use nn.ModuleList to register each sub-modules.
self.feature_extractors = nn.ModuleList()
for window_size, n_filter in zip(window_sizes, n_filters):
self.feature_extractors.append(
nn.Sequential(
nn.Conv2d(
in_channels=1, # We only use one embedding layer.
out_channels=n_filter,
kernel_size=(window_size, word_vec_size),
),
nn.ReLU(),
nn.BatchNorm2d(n_filter) if use_batch_norm else nn.Dropout(dropout_p),
)
)
# An input of generator layer is max values from each filter.
self.generator = nn.Linear(sum(n_filters), n_classes)
# We use LogSoftmax + NLLLoss instead of Softmax + CrossEntropy
self.activation = nn.LogSoftmax(dim=-1)
def forward(self, x):
# |x| = (batch_size, length)
x = self.emb(x)
# |x| = (batch_size, length, word_vec_size)
min_length = max(self.window_sizes)
if min_length > x.size(1):
# Because some input does not long enough for maximum length of window size,
# we add zero tensor for padding.
pad = x.new(x.size(0), min_length - x.size(1), self.word_vec_size).zero_()
# |pad| = (batch_size, min_length - length, word_vec_size)
x = torch.cat([x, pad], dim=1)
# |x| = (batch_size, min_length, word_vec_size)
# In ordinary case of vision task, you may have 3 channels on tensor,
# but in this case, you would have just 1 channel,
# which is added by 'unsqueeze' method in below:
x = x.unsqueeze(1)
# |x| = (batch_size, 1, length, word_vec_size)
cnn_outs = []
for block in self.feature_extractors:
cnn_out = block(x)
# |cnn_out| = (batch_size, n_filter, length - window_size + 1, 1)
# In case of max pooling, we does not know the pooling size,
# because it depends on the length of the sentence.
# Therefore, we use instant function using 'nn.functional' package.
# This is the beauty of PyTorch. :)
cnn_out = nn.functional.max_pool1d(
input=cnn_out.squeeze(-1),
kernel_size=cnn_out.size(-2)
).squeeze(-1)
# |cnn_out| = (batch_size, n_filter)
cnn_outs += [cnn_out]
# Merge output tensors from each convolution layer.
cnn_outs = torch.cat(cnn_outs, dim=-1)
# |cnn_outs| = (batch_size, sum(n_filters))
y = self.activation(self.generator(cnn_outs))
# |y| = (batch_size, n_classes)
return y
6. 쉬어가기) Multi-Label Classification
Mutli-Label Classification: 기존 softmax 분류와 달리 여러 클래스가 동시에 정답이 될 수 있는것
6.1 Binary-Classification sigmoid. &. BCELoss를 사용한다. (이진분류상황은 Bernoulli Distribution이기 때문)
수식은 아래와 같은데, BCE Loss는 이진분류에 특화된 기존 CE Loss의 한 종류이다. 이 수식에서 y는 0또는 1을 갖는 불연속적인 값이고 y_hat은 sigmoid를 통과한 0~1사이의 연속적인 출력값이다.
6.2 Multi-Binary Classification 그렇다면, Multi-Label문제에서 Binary Classification를 어떻게 적용할까?
n개의 항목을 갖는 분류에 대해 신경망의 마지막 계층에 n개의 노드를 주고, 모두 sigmoid함수를 적용한다. 즉, 하나의 모델로 여러 이진분류작업이 가능하다.
그렇다면 최종 손실함수는? 다음과 같다.
6.3ETC 이진분류가 아닐 때는, sigmoid가 아닌 softmax를 사용하고, Loss도 Cross-Entropy로 바꾸면 된다.
마치며...
이번시간에는 text classification에 대해 다루었다. text classification은 모델의 구조의 복잡도나 코드작성난도에 비해 활용도가 매우 높은 분야이다. 다만, 신경망사용이전, 불연속적값에 대한 희소성문제해결을 하지 못한 채, Naïve Bayes방식과 같이 매우 간단하고 직관적인 방법을 사용했다. 다만, Naïve Bayes방식은 "각 feature는 independent하다!"라는 강력한 가정으로인해 classification의 정확도가 떨어질 수 밖에 없었다. 하지만 딥러닝의 도입으로 매우 효율적이고 정확하게 text classification이 가능해졌는데, RNN은 단어들을 순차적으로 받아 가장 마지막 time-step에서 classification을 예측하고 CNN은 classification에 중요한 단어들의 조합에 대한 패턴을 감지하기도 한다.
∙RNN의 경우, 문장전체의 맥락과 의미에 더 집중해 classification을 수행하며 ∙CNN의 경우, 해당 클래스를 나타내는 단어조합에 대한 pattern의 유무를 가장 중시한다.
따라서 RNN과 CNN을 조합해 Ensemble Model로 구현한다면 더 좋은 결과를 얻을수도 있다. 이를 기반으로 다른 모델들을 참고한다면, 긴문장이나 어려운 텍스트에서도 더 높은 성능을 낼 수 있을 것이다.
자연어 처리분야는 문장 내 단어들이 앞뒤 위치에 따라 서로 영향을 주고받는다. 따라서 단순히 y = f(x)같은 순서의 개념 없이 입력을 넣으면 출력이 나오는 함수의 형태가 아닌, 순차적(sequential)입력으로 입력에 따른 모델의 hidden state가 순차적으로 변하며, 상태에 따라 출력결과가 순차적으로 반환되는 함수가 필요하다.
이런 시간개념이나 순서정보를 사용해 입력을 학습하는 것을 sequential modeling이라 한다. 신경망으로는 RNN 등으로, 신경망 뿐만 아니라 HMM, CRFs(Hidden Markov Model이나 Conditional Random Fields)등의 다양한 방법으로 위의 문제에 접근할 수 있다.
2. RNN
2.1 Feed Forward 기본적인 RNN을 활용한 순전파 계산흐름을 알아보자. 다음 그림은 각 time-step별로 입력 xt와 이전 time-step ht가 RNN으로 들어가 출력 o를 반환한다. 이렇게 도출한 o들을 y_hat으로 삼아서 정답인 y와 비교 후, 손실 L을 계산한다.
이를 수식으로 표현하면 다음과 같다.
2.2 BPTT (Back Propagation Through Time) 순전파 이후 time-step의 RNN에 사용된 parameter θ는 모든 시간에 공유되어 사용된다. 따라서 앞서 구한 손실 L에 미분을 통해 역전파를 수행하면, 각 time-step별로 뒤로부터 θ의 기울기가 구해지고, 이전 time-step(t-1)θ의 기울기에 더해진다. 즉, t가 0에 가까워질수록 RNN의 parameter θ의 기울기는 각 time-step별 기울기가 더해져 점점 커진다. 아래 그림에서 좌측으로 갈수록 기울기가 더해져 점점커지는 속성을 갖는데, 이 속성을 '시간 축에 대해 수행되는 역전파 방법'이라는 뜻으로 BPTT라 한다.
이런 RNN 역전파의 속성으로 인해, RNN은 마치 time-step 수만큼 layer가 존재하는 것과 같은 상태가 되므로 time-step이 길어질수록 Deep RNN과 유사하게 동작한다.
2.3 Gradient Vanishing 앞서 설명했듯, BPTT로인해 RNN은 역전파 시 마치 time-step만큼의 계층이 있는것과 비슷한 속성을 갖는다. 하지만 앞의 RNN 수식을 보면 활성화함수로 tanh함수가 사용된다. tanh의 양 끝 기울기가 -1과 1로 수렴(기울기는 0에 근접)한다. 따라서 tanh 양 끝의 값을 반환하는 층의 경우, 기울기가 0에 가까워진다. 이렇게되면 그 다음으로 미분값을 전달받은 층은 제대로된 미분값(기울기)을 전달받을 수 없게 된다. y>0: sigmoid함수, -1~1: tanh함수 추가적으로 tanh와 sigmoid의 도함수는 모두 기울기 값이 1보다 작거나 같으므로 ❗️층을 거칠수록 기울기의 크기는 작아질 수 밖에 없다!! → gradient vanishing
tanh'(0)=1 , sigmoid'(0)=0.5 값을 갖는다.
따라서 RNN같이 time-step이 많거나 여러층을 갖는 MLP의 경우, 이런 기울기소실문제가 쉽게 발생한다. 다만 MLP에서ReLU와 Skip-Connection의 등장으로 너무 큰 문제가 되는 것은 아니다.
2.4 Deep RNN 기본적으로 time-step별 RNN이 동작한다. 다만, 아래처럼 하나의 time-step내에서 여러층의 RNN을 쌓을 수도 있다. 당연히 층별로 parameter θ를 공유하지 않고 따로 갖는다. 하나의 층만 갖는 기존 RNN의 경우, hidden state와 출력값이 같은 값이었다. 여러 층이 쌓여 이뤄진 RNN의 경우, 각 time-step의 RNN 전체 출력값은 맨 위층 hidden state가 된다. 출력텐서의 크기의 경우 다음과 같다. ∙ 단일 RNN: |h1:n| = (batch_size, n, hidden_size) ∙ Deep RNN: |ht| = (#layers, batch_size, hidden_size)
2.5 Bidirectional RNN 이제 RNN의 방향에 관해 이야기해보자. 양방양 RNN을 사용하면 기존 정방향에 역방향까지 추가되어 마지막 time-step에서부터 거꾸로 역방향(reverse direction)으로 입력받아 진행한다. 당연히 정방향과 역방향의 parameter θ는 공유되지 않는다.
결과적으로, output은 과거와 미래 모두에 의존할 수 있게 되는 것이다.
출력텐서의 크기의 경우 다음과 같다. ∙ |ht| = (#direction × #layers, batch_size, hidden_size)
2.6 NLP 적용사례
2.7 정리 NLP에서 거의 대부분의 입출력형태는 모두 불연속적인 값을 갖는다. 즉, regression보다는 classification에 가깝다. 따라서 Cross-Entropy Loss function을 사용해 신경망을 train한다.
이처럼 RNN은 가변길이의 입력을 받아 출력으로 가변길이를 반환하는 모델이다. 하지만 가장 기본적인 Vanilla-RNN은 time-step이 길어질수록 앞의 data기억이 어렵다.
3. LSTM (Long Short Term Memory)
3.1 LSTM ∙ RNN은 가변길이의 sequential data형태 입력에 잘 작동하지만 그 길이가 길어질수록 앞서입력된 data를 까먹는 치명적인 단점이 존재한다. 이를 보완하고자 LSTM이 도입되었다. (여전히 긴 길이의 data에 대해 기억하지는 못함, 보완만 함)
LSTM은 기존 RNN의 은닉상태 이외에 별도의 cell state를 갖게하여 기억력을 증강한다. 추가적으로 여러 gate를 둬 forget, output 등을 효과적으로 제어한다. 그 결과, 긴 길이의 data에 대해서도 효율적으로 대처할 수 있게 되었다.
다만, 구조적으로 더욱 복잡해져서 더 많아진 parameter 학습을 위해 더 많은 data를 이용해 훈련해야한다. 아래는 LSTM의 수식이다.
각 gate의 sigmoid(σ)가 붙어 0~1값으로 gate를 얼마나 열고 닫을지를 결정한다.
3.2 LSTM의 역전파
4. GRU (Gated Recurrent Unit)
4.1 GRU GRU는 LSTM의 간소화 버전으로 기존에 비해 더 간단하지만 성능이 비슷한 것이 특징이다. σ로 구성된 rt(reset gate)와 zt(update gate)가 존재한다. σ로 여전히 data의 흐름을 열고 닫아 제어할 수 있으며, 기존 LSTM대비 gate의 숫자는 줄고 따라서 gate에 딸려있는 parameter 수도 그만큼 줄어든다. GRU의 수식은 아래와 같다. GRU는 LSTM보다 몸집이 작긴하지만 LSTM이 현저히 사용빈도가 더 높은데, 성능차이보다는 LSTM과 GRU의 학습률, hidden_size등의 hyper-parameter가 다르기에 사용모델에따라 parameter setting을 다시 찾아내야 한다.
5. Gradient Clipping
5.1 Gradient Clipping RNN은 BPTT(Back Propagation Through Time)을 통해 시간역행으로 기울기를 구한다. 매 time step마다 RNN의 parameter에 기울기가 더해지므로 출력의 길이에따라 기울기크기가 달라진다. 즉, 길이가 길수록 자칫 기울기의 크기인 norm이 너무 커지는, gradient exploding문제가 야기될 수 있다.
❗️기울기의 크기가 너무 커질 때, 가장 쉬운 대처법: 학습률을 아주 작게 설정 다만, 훈련속도가 매우 느려질 수 있다는 단점이 존재하며 local optima에 빠질 수 있음 즉, 길이가 가변이기에 학습률을 매번 알맞게 최적의 값을 찾는 것은 무척 어렵기에 이때, Gradient Clipping이 큰 위력을 발휘한다.
Gradient Clipping은 parameter θ의 norm(보통 L2 norm)를 구하고 이 norm의 크기를 제한하는 방법이다. 즉, gradient vector는 유지, 크기를 학습에 영향주지 않는 만큼 줄이는 것이다.
수식을 보면, 기울기 norm이 정해진 최대값(threshold)보다 크다면 최대값보다 큰 만큼의 비율로 나눠준다. 결과적으로 항상 기울기는 threshold보다 작아지게 되며, 이는 학습의 발산을 방지하고 기울기의 방향자체를 바꾸지 않고 유지시켜 parameter θ가 학습해야하는 방향성을 잃지 않게 해준다.
즉, 손실함수를 최소화하기 위한 기울기의 방향은 유지하고, 크기만 조절하기에 학습률을 1과 같이 큰 값으로도 학습에 사용가능하다. 다만, Adam과 같은 동적 학습률을 갖는 optimizer는 사용할 필요성이 없고 SGD와 같은 경우 적용하는 편이 좋다.
아래와 같이 pytorch에서 Gradient Clipping기능을 사용할 수 있다.
import torch.optim as optim
import torch.nn.utils as torch_utils
learning_rate = 1.
max_norm = 5
optimizier = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=0.01)
# 기울기폭발을 피하기 위해 gradient clipping을 도입
torch_utils.clip_grad_norm_(model.parameters(), max_grad_norm)
optimizer.step()
마치며...
이번시간에는 RNN을 활용한 순서정보를 가진 순차데이터, 시계열데이터를 학습하는 방법을 익혔다. 기존의 신경망과 달리 RNN은 이전 time step의 자기자신을 참조해 현재 자신의 state를 결정한다. 따라서 time step마다 RNN의 신경망가중치는 공유되지만 기울기소실, 장기기억 등의 문제로 긴 순차데이터처리에 어려움을 겪는다. LSTM과 GRU의 경우, 이런 RNN의 단점을 보완해 여러 gate를 열고 닫아(by sigmoid) 정보의 흐름을 조절함으로써 장기기억력에 더 나은 성능을 보여준다. RNN의 역전파알고리즘인 BPTT는 시간에 대해서도 이뤄지는데, time-step이 많은 data일수록 time-step별 기울기가 더해져 최종기울기가 커진다. 기울기가 클 때, 너무 큰 학습률을 사용하면 해당 학습은 발산할 가능성이 높다.
따라서 기울기가 정해진 임계치보다 커지지 않도록하는 Gradient Clipping을 통해 방향은 유지, 크기만 감소시켜 학습률 1과 같은 매우 큰 값도 학습에 사용할 수 있게 해준다.
🧐 RNN (RecurrentNeuralNetwork) 일반적으로 i의 단어는 그 이전에 발생한 i-1개 단어와 상호작용한다.
RNN
🧐 Long Term Dependency과 LSTM(LongShort-Term-Memory)
Long Range Dependency: RNN은 시간을 처리할 수 있는 능력을 갖추지만 길이가 긴 sample에는 한계가 있다. hi는 1, 2, ..., i 순간에 발생하는 단어의 정보가 혼합되기에 오래된 단어의 정보는 희미해진다. 즉, 앞쪽단어와 멀리있는 단어가 밀접하게 상호작용하는 long-range dependency를 제대로 처리하지 못하는 문제가 발생한다.
LSTM: RNN을 개조해 long-term dependency를 처리하는 능력을 강화한다. input과 output을 열거나 막는 gate를 두어 선별적으로 기억하는 기능으로 여닫는 정도를 조절한다. 이때, 여닫는 정도는 학습으로 알아낸 가중치로 결정된다.
ex) 한국어→영어로 번역 시, 둘의 문장길이가 달라 seq2seq model이 필요하다.- 학습 시 Decoder의 input부분과 output부분이 모두 동작한다. 즉, 정답에 해당하는 출력을 알려주는 교사강요(teacher forcing)방법을 사용한다.
- 예측 시 정답을 모르기 때문에 위의 회색표시한 input 부분을 제외하고 자기회귀(auto-regressive) 방식으로 동작한다. 자기회귀에서 <SOS>가 입력되면 첫 단어 'That'을 출력하고 'That'을 보고 그 다음 둘째 단어 'can't'를 출력한다. 즉, 이전에 출력된 단어를 보고 현재단어를 출력하는 일을 반복하며, 문장끝을 나타내는 <EOS>가 발생하면 멈춘다.
- 한계 : 가장 큰 문제는encoder의 마지막 hidden state만 decoder에 전달한다는 점이다. 그림에서 보면h5만 decoder로 전달된다. 따라서 encoder는마지막 hidden state에 모든 정보를 압축해야하는 부담이 존재한다.
[query-key-value로 계산하는attention] _ NLP
🧐 query-key-value
QKV: attention을 계산하는 방법을 여러가지인데, 최근에는 query가 key와 유사한 정도를 측정하고 유사성 정보를 가중치로 사용해 value를 가중합하는 방법을 주로 사용한다.
벡터의 차원을d라 하면,q는 1xd행렬이고K와V는nxd행렬이다. 이때,n은 key와 value가 갖는 벡터의 개수이다.
[seq2seq withAttention] _ NLP
seq2seq는 입력문장의 모든 정보를 encoder의 마지막 hidden stateh5에 압축해 넣어야하는 부담이 있다. Bahdanau는 아래 식의 attention을 이용해 decoder가 encoder의 모든 state(h1,h2, ... ,h5)에 접근할 수 있게 허용하여 성능을 향상한다.[Bahdanau2014;https://arxiv.org/abs/1409.0473]
🧐Bahdanau Attention 위의 seq2seq의 경우, decoder는 i=6일 때, encoder가 i=2일 때, 단어 '저절로'에 주목해야 'itself'라는 단어를 제대로 생성할 수 있다. decoder가 i=6일 때, (.01 .9 .02 .03 .04)처럼 2번째 원소값이 큰 attention vectora를 생성하면 encoder의 두번째 단어 '저절로'에 더 주목할 수 있어 성능이 향상될 수 있다.
- 이에 대해 query, key, value가 무엇이 되어야 위와 같이 될 수 있는지 살펴보자.
🤫 RNN, 순환 신경망이란? RNN은 특정시점 데이터를 한번에 수집하는 방법들과 다르게 sequence data(시간의 흐름에 따라 값이 달라지는 데이터)를 사용한다. 또한 지금까지 다룬 신경망은 feed forward 신경망 즉, 한번 출력된 결과를 다시 사용하지 않았지만 RNN의 경우, 출력결과를 다음시점까지 기억했다가 다음 시점에서 사용하는 방법이다.
가장 일반적인AI딥 러닝을 사용한 시계열 작업에 대한 접근 방식은 순환 신경망(RNN)인데, RNN을 사용하는 이유는 시간에 대한 해결의 일반화에 있다.시퀀스는 (대부분) 길이가 다르기 때문에 MLP 같은 고전적인 딥러닝 구조는 수정하지 않고는 적용할 수 없고MLP의 가중치 수는 absolutely huge하다.따라서 전체 구조에서 가중치가 공유되는 RNN을 일반적으로 사용한다. (in sequential data)
activation function이 tanh일 때, 아래와 같은 과정으로 수식이 진행된다. 다만 은닉층에서 편향 벡터(bias vector)가 있다면 이전처럼 더해주면 된다.
🤫 RNN의 종류
🤫 시간을 통한 Backpropagation
single time step일 때, 아래와 같은 단계로 진행된다. input이 hidden layer를 거쳐 output에 도착하는데, 이때 실제와 예측의 차이계산을 위해 Loss함수가 계산된다. 전체 Loss가 계산되면 forward propagation이 완료되며 그 다음부터 도함수를 이용해 역전파를 진행한다.
🧐 LSTM (Long Short Term Memory)
🤫 LSTM 의 등장배경: RNN의 Gradient 소실, 폭주 문제 RNN은 은닉층을 거친 결과값을 재사용하기에 그로 인해 gradient 소실 및 폭주 문제가 발생할 수 있다.
📌 Exploding Gradient Problem - 구간이 기하급수적으로 빠르게 무한대로 이동하고 불안정한 프로세스로 해당값이 NaN이 된다.
📌 Vanishing Gradient Problem - 구간이 기하급수적으로 빠르게 0이 되어 장기간 학습이 어려워 진다. - 이에 대한 해결을 위해 다양한 방식의 접근이 시도되었는데, 다음과 같다. ∙ ReLU 활성화 함수 사용 ∙ Forget Gate를 이용한 LSTM 구조 ∙ weight matrix W를 orthogonal matrix로 초기화, 전체 training에 사용 (orthogonal은 explode되거나 vanish되지 않음)
🤫 LSTM이란?
아래구조처럼 입력 게이트(input gate), 삭제 게이트(forget gate), cell state, hidden unit으로 나눌 수 있다.
🧐 GRU (Gated Recurrent Unit)
🤫GRU GRU는 LSTM과 비슷한 원리로 작동하지만 조금 더 간소화된 방식으로 계산이 간편하다는 장점이 있다. 하지만 LSTM보다 학습능력이 다소 낮은면을 보인다.
LSTM과 다르게 GRU는 이전 시점에서 받는 벡터가 h하나이다. 또한 벡터 z가 입력과 삭제게이트를 모두 제어한다. GRU에는 출력게이트가 없는데, 즉 전체 상태 벡터가 매 시점 출력된다는 뜻이다.