개발자의시작

[Pytorch] 11-2 RNN hihello and charseq 본문

머신러닝(machine learning)

[Pytorch] 11-2 RNN hihello and charseq

LNLP 2022. 3. 14. 20:20

이 글은 모두를위한딥러닝 시즌2 https://github.com/deeplearningzerotoall/PyTorch 을 정리한 글입니다.

 

GitHub - deeplearningzerotoall/PyTorch: Deep Learning Zero to All - Pytorch

Deep Learning Zero to All - Pytorch. Contribute to deeplearningzerotoall/PyTorch development by creating an account on GitHub.

github.com

 

이번 chapter에서는 RNN을 이용하여 다음 문자열을 예측하는 hihello 코드와 charseq 코드를 자세히 살펴본다.

 

"hihello" problem

 

이 예제에서 풀고자 하는 문제는 "hihello"라는 문자열을 예측하는 모델을 만드는 것. 조금 더 구체적으로 설명하면, "h"가 들어오면 "i"를 예측하고, "i"가 들어오면 "h"와 같이 char가 들어오면 다음 char를 예측하는 모델을 만드는 것. 단순히 이전 문자만 본다면 대부분의 char는 다음에 나올 수 있는 char가 하나여서 문제가 없지만, "h"의 경우 "i" 또는 "e"가 나올 수 있고 "l"은 "l" 또는 "o"가 나올 수도 있다. 그렇기 때문에 모델이 어디까지 진행되었는지 저장하는 RNN의 hidden state의 역할이 매우 중요하다. 아래에서는 PyTorch가 문자를 어떻게 표현하는지 살펴본다.

 

char를 컴퓨터상에서 표현하는 방법은 다양하지만, 가장 간단한 방법은 각각의 char들을 index로 표현하는 것이다. 하지만 이와 같이 단순히 번호를 매기는 것은, index 번호의 크기에 따라 의미는 없지만 마치 의미를 부여한 것과 같은 효과를 가지게 된다. 따라서 continuous하지 않고 categorical 한 데이터를 표현할 때는 one-hot encoding이라는 방식을 주로 사용한다.

one-hot encoding은 사실 매우 단순하다. 위의 그림에서 보다시피 벡터의 하나의 축에서만 1로 표현되고 나머지는 모두 0으로 표현되는 방식이다. 각각의 char에 index에 해당하는 위치만 1로 표현하고 나머지는 0으로 표현하는 방식이다. 입력 데이터를 보면 "o"를 나타내는 one-hot 벡터는 가지고 있지 않는데, 이 모델은 "hihello"에서 다음 문자를 예측하는 모델이기 때문에 "hihello"에서 "o"를 뺀 "hihell"만 입력으로 사용되고, "h"를 뺀 "ihello"가 예측할 답으로 사용된다. 

이번에는 Cross Entropy Loss에 대해 살펴본다. 이번 "hihello" 예제에서는 Cross Entropy라는 Loss를 사용하는데 이 Loss는 categorical 한 output을 예측하는 모델에서 주로 사용된다. 일반적으로 categorical 한 output을 만드는 모델에서는 output값들을 softmax 함수 등을 사용하여 확률 값으로 다음과 같이 표현한다. 이때 정답 category인 경우 이 확률 값(0.4)을 정답 카테고리에 대해 최대한 올리게끔 하는 것이 이 Cross Entropy Loss의 역할이라 보면 된다. PyTorch에서는 위의 코드와 같이 Cross Entropy Loss를 정의하고 정의한 Loss term에 대해 함수로 사용할 수 있다. 여기서 주의해야 할 점은 함수의 첫 번째 파라미터로 모델의 output을 주어야 하고 두 번째 파라미터로는 정답 label을 주어야 한다. 만약 이 순서를 바꿔서 입력하면 제대로 학습이 이루어지지 않을 수 있다.

 

다음의 예는 "hihello" 예제 코드이다. 먼저 모델을 만들기 전 하이퍼 파라미터들의 정의 그리고 one-hot encoding을 이용하여 input data를 만들어주는 부분이다. 처음에는 어떤 char들이 있는지에 대한 char 리스트에 대한 정의이다. 그리고 input size는 one-hot encoding을 해야 하기 때문에 char의 개수들만큼 사이즈를 갖는다. hidden size는 다른 숫자여도 상관없지만, 여기서는 단순하게 input size와 갖게 하였다. 최적화를 진행하면서 weight 업데이트를 할 때 사용되는 learning rate는 0.1로 한다. 그 아래 부분은 char를 index로 표현한 것과, 이들을 one-hot encoing으로 나타낸 것이다. 그리고 y data는 "hihello"에서 처음 "h"를 뺀 나머지 부분에 대해 index로 표현한 데이터이다. 그리고 마지막으로 파이썬 list로 구성된 데이터를 PyTorch의 Tensor로 바꾸어주는 변환작업을 거치게 된다. 여기까지가 "hihello" 코드의 간단한 데이터 준비 과정이다. 

 

다음에 볼 코드는 charseq 코드의 데이터 준비 과정이다. charseq 코드는 "hihello"의 데이터 준비 과정을 조금 더 일반화한 것이라고 생각하면 된다. 먼저 "hihello"에서는 "hihello"라는 스트링에만 맞는 코드였지만, charseq 코드는 어떤 문자열이라도 sample에 들어가면 동작할 수 있도록 설계된다. char들의 집합은 파이썬의 set함수를 사용해 list로 생성한다. 그다음 특정 char를 주면 index로 알아서 찾아주는 char dictionary를 만드려고 하는데 파이썬 내장 함수인 enumerate를 사용하면 인덱스와 char를 같이 가져올 수 있고, 이것을 char to index로 매핑하여 dictionary를 만들 수 있다. 그리고 이전 "hihello"와 같이 하이퍼 파라미터를 설정한다. "hihello"에서는 index를 직접 숫자로 지정해주었지만, 여기서는 어떠한 sample에 대해서도 동작하도록 만들기 위해 char dictionary를 이용해 index들을 구해준다. 여기서 x_data는 맨 마지막 문자를 뺀 문자열이고 y_data는 첫 번째 문자를 뺀 문자열이다. one-hot encoing의 경우 x_data의 index들을 가지고 one-hot vector를 만들어야 한다. 이때 np.eye라는 함수를 사용해 간단히 만들 수 있는데, np.eye는 identity matrix로 만들어 주는 함수로, 특정 x번째 입력에 대한 one hot vector를 가져올 수 있다. 마지막으로 numpy와 list로 구성된 데이터들을 PyTorch의 Tensor로 맞추어주는 변환작업을 수행한다. 

여기서부터는 RNN을 만들고 학습하는 부분을 나타낸다. 이전 chapter에서 배운 것과 같이 RNN을 정의한다. 주석과 같이 "batch_first"를 "True"로 하면 batch dimension이 가장 앞으로 오게 된다. 그리고 밑에 두 줄은 Cross Entropy Loss에 대한 정의와, 최적화를 진행할 때 쓸 optimizer에 대한 정의이며 여기서는 adam optimizer를 사용한다. "for loop" 안이 training을 진행하는 부분인데, PyTorch에서 가장 처음 해주어야 할 것이 "optimizer.zero_grad()"이다. 이것을 꼭 해주어야 매 loop마다 새로 gradient를 구할 수 있고, 만약 해주지 않으면 기존 구했던 gradient에 축적이 되면서 training이 정상적으로 진행하지 않는다.

 

그다음 outputs를 구하는 부분은 RNN에 input X를 넣어 구하고, _status는 만약 다음 input이 있으면 그다음 input을 RNN의 안에서 계산할 때 사용할 hidden state이다. 여기서는 주어진 모든 input을 다 처리하고 나오는 hidden state이기 때문에 따로 쓰이지는 않는다. 

 

다음으로 outputs과 y의 shape를 batch dimension이 앞으로 오도록 바꾸어준 다음 loss를 계산하게 된다. 그 후 loss.backward()라는 함수를 실행하면 backpropagation을 수행하게 되고, gradient 값을 구한다. 그 후 이 gradient 값들을 토대로, optimizer.step()를 진행하게 되면 이전에 optimizer에 넣어 두었던 파라미터들에 대해 업데이트를 하게 된다. 

 

코드의 마지막 세줄은 실제로 모델이 어떻게 예측했는지 알아보기 위한 코드이다. 먼저 outputs를 numpy array로 가져오고, argmax(axis=2)를 해주면 index가 2인 dimension(즉, 어떤 char인지 나타내는 dimension)에서 어떤 char가 가장 가능성 있는지 값을 가지고 있는데 이 중 가장 큰 값을 가지는 index를 가져오는 함수가 argmax이다. 이렇게 가장 큰 index 들만 가져온 후 char set에서 이 index가 어떤 char에 해당하는지 가져오고 나서 join 함수를 사용하여 하나의 string으로 만들어 준다. 여기서 쓰는 np.squeeze()는 shape에서 dimension이 1인 축을 없애주는 함수이다. 

 

'머신러닝(machine learning)' 카테고리의 다른 글

[Pytorch] 11-4 RNN timeseries  (0) 2022.03.16
[Pytorch] 11-3 RNN - longseq  (0) 2022.03.15
[Pytorch] 11-1 RNN basics  (0) 2022.03.03
[Pytorch] 11-0 RNN intro  (0) 2022.03.03
[Pytorch] 09-4 Batch Normalization  (0) 2022.03.03
Comments