300x250

PyTorch를 간단히 다루어본 적이 있는데, 앞으로의 연구에 익숙하게 활용하기 위해 PyTorch 내용을 정리해보려 한다.

대부분의 내용은 유튜브의 '모두를 위한 딥러닝 시즌2'를 참고하였다.

기본적인 내용과 파이썬 문법은 어느 정도 알고 있다고 가정하고, PyTorch 실습과 관련하여 정리해두었다.

 

RNN의 개념과 가장 간단한 형태의 실습 내용이 궁금하면 이전 포스팅을 참조하자.

https://jjuke-brain.tistory.com/entry/PyTorch-%EC%9D%B5%ED%9E%88%EA%B8%B0-Recurrent-Neural-Network-RNN

 

PyTorch 익히기 - Recurrent Neural Network (RNN) (1)

PyTorch를 간단히 다루어본 적이 있는데, 앞으로의 연구에 익숙하게 활용하기 위해 PyTorch 내용을 정리해보려 한다. 대부분의 내용은 유튜브의 '모두를 위한 딥러닝 시즌2'를 참고하였다. 기본적인

jjuke-brain.tistory.com

 

 

https://www.youtube.com/watch?v=KBX8tSfJ49A&list=PLQ28Nx3M4JrhkqBVIXg-i5_CVVoS1UzAv&index=31 

 

 

 

목차

     

     

     

     

    4. Long Sequence

     

    이전 포스팅에서 가장 간단한 RNN 실습(hihello, charseq 예제)을 해보았다.

    이제 조금 더 긴 문장을 다뤄보자.

     

    이번 예제는 'longseq'라는 예제이다.

    짧은 문장에서는 문장 전체를 하나의 샘플로 보고 모델을 설계했지만, 실제 RNN 모델을 적용할 때에는 매우 긴 문장을 다루게 될 것이다.

    긴 문장을 매번 하나의 input으로 사용할 수는 없으므로, 특정 사이즈로 잘라야 할 것이다.

     

     

     

     

    1) Making sequence dataset from long sentence

     

    다음과 같은 문장이 있다고 하자.

     

    "if you want to build a ship, don't drum up people together to "

    "collect wood and don't assign them tasks and work, but rahter "

    "teach them to long for the endless immensity of the sea."

     

    여기서 특정 size의 window를 사용하여 한 글자씩 오른쪽으로 움직이며 반복적으로 입출력을 결정한다.

    위 문장에서 만약 size가 10인 window를 사용한다면,

    • 첫 번째 입력 : "if you wan" → 첫 번째 출력(정답) : "f you want"
    • 첫 번째 입력 : "f you want" → 첫 번째 출력(정답) : " you want "
    • 첫 번째 입력 : " you want " → 첫 번째 출력(정답) : "you want t"
    • 첫 번째 입력 : "ou want to" → 첫 번째 출력(정답) : "u want to "
    • ...

     

    와 같은 순서로 학습을 시킬 수 있다.

    이를 코드로 나타내면 다음과 같다.

     

    # data setting
    x_data = []
    y_data = []
    
    for i in range(0, len(sentence) - sequence_length):
    	x_str = sentence[i :i + sequence_length]
        y_str = sentence[i + 1 : i + sequence_length + 1]
        print(i, x_str, '->', y_str)
        x_data.append([char_dic[c] for c in x_str]) # x str to index
        y_data.append([char_dic[c] for c in y_str]) # y str to index
        
    x_one_hot = [np.eye(dic_size)[x] for x in x_data]
    
    # transform as torch tensor variable
    X = torch.FloatTensor(x_one_hot)
    Y = torch.LongTensor(y_data)

     

     

     

     

    2) Stacking RNN and Adding FC Layer

     

    간단한 모델(Vanilla RNN)에서는 하나의 셀(RNN Layer)을 사용했었다.

    하지만, 입력 사이즈가 커지고 복잡한 task일수록 더 깊은 모델이 필요할 수 있다.

    따라서 그림과 같이 RNN을 여러 번 쌓고(Stacking), Fully Connected Layer를 추가해줄 수 있다.

     

    Stacking RNN and Adding FC Layer

     

    이렇게 모델을 설계하는 코드는 다음과 같다.

     

    class Net(torch.nn.Module):
    	def __init__(self, input_dim, hidden_dim, layers): # 어떤 하위 모듈을 사용하는지 정의
        	super(Net, self).__init__()
            self.rnn = torch.nn.RNN(input_dim, hidden_dim, num_layers=layers,
            						batch_first=True)
    		self.fc = torch.nn.Linear(hidden_dim, hidden_dim, bias=True)
        
        def forward(self, x):  # input을 넣어 어떻게 계산할지 정의
        	x, _status = self.rnn(x)
            x = self.fc(x)
            return x
    
    net = Net(dic_size, hidden_size, 2)

     

    이후에 loss function과 optimizer 정의, training 과정은 이전 코드와 비슷하다.

     

    # Loss & optimizer
    criterion = torch.nn.CrossEntropyLoss()
    optimizer = optim.Adam(net.parameters(), learning_rate)
    
    # start training
    for i in range(100):
    	optimizer.zero_grad()
        outputs = net(X)
        loss = criterion(outputs.view(-1, dic_size), Y.view(-1))
        loss.backward()
        optimizer.step()
        
        results = outputs.argmax(dim=2)
        predict_str = ""
        for j, result in enumerate(results):
        	print(i, j, ''.join([char_set[t] for t in result]), loss.item())
            if j == 0:
            	predict_str += ''.join([char_set[t] for t in result])
            else:
            	predict_str += char_set[result[-1]]

     

     

     

     

     

     

    5. Seq2Seq

     

    Seq2Seq 모델은 sequence를 입력 받아 sequence를 출력하는 모델이다.

    일반적인 RNN과 Seq2Seq 모델을 간단히 다음과 같이 나타낼 수 있다.

     

    RNN vs Seq2Seq

     

    앞서 RNN 개념을 간단히 설명할 때, many-to-many 모델에 대해 설명했었다. 그때 위와 같이 나뉜다고 하였는데, Seq2Seq는 어떤 sequence의 입력이 끝난 후에 그에 따른 sequence를 출력해준다.

     

    대표적인 예시로는 챗봇, 기계 번역 등이 있다. 입력이 모두 주어진 다음 출력을 하는 것이 적절한 경우이다.

     

     

     

     

    1) Seq2Seq의 구조

     

    Seq2Seq 모델의 대표적인 특징은 바로, Encoder와 Decoder 구조이다.

     

    Encoder and Decoder

     

    각각의 역할은 다음과 같다.

    • Encoder : 입력 sequence를 벡터 형태로 압축하여 decoder에 전달해준다.
    • Decoder : Encoder의 결과 벡터를 첫 셀의 Hidden State로 받아와 RNN 모델을 적용한다.

     

    Encoder와 Decoder를 실제 학습에 사용하는 예시(기계 번역) 코드는 다음과 같이 매우 간단하다.

     

    enc_hidden_size = 16
    dec_hidden_size = enc_hidden_size # 같은 크기로 정의
    enc = Encoder(load_source_vocab.n_vocab, enc_hidden_size).to(device) # RNN layer
    dec = Decoder(dec_hidden_size, load_target_vocab.n_vocab).to(device) # RNN layer

     

    Encoder의 클래스를 한 번 살펴보자. (예시)

     

    class Encoder(nn.Module):
    	def __init__(self, input_size, hidden_size):
        	super(Encoder, self).__init__()
            self.hidden_size = hidden_size
            self.embedding = nn.Embedding(input_size, hidden_size)
            self.gru = nn.GRU(hidden_size, hidden_size) # RNN 중 GRU라는 모델 사용
        
        def forward(self, x, hidden):
        	x = self.embedding(x).view(1, 1, -1)
            x, hidden = self.gru(x, hidden)
            return x, hidden

     

    여기서 Embedding이란, 수치화되지 않은 자연어 등의 데이터를 특징을 추출(벡터화)하는 과정을 말한다

     

    위 예시에서 Embedding layer의 input_size는 Source text 내부 단어의 개수로 볼 수 있다. 실제 번역을 하기 위해서는 매우 많은 단어를 입력으로 받게 될 것인데, 특징을 추출함으로써 그렇게 큰 차원을 hidden_size만큼으로 줄여주는 것이 embedding의 역할이다.

     

    조금 수학적으로 보자면, Embedding은 거대한 matrix로 볼 수 있다.

    앞 글에서 단어 내부의 글자 종류에 따라 one-hot-encoding을 하는 과정을 보았다. 마찬가지로, 문장 내에서 단어를 기준으로 one-hot-encoding을 진행할 수도 있을 것이다.

    Embedding Matrix에 이러한 벡터(input_size, 큰 차원을 가짐)를 곱해주면 hidden_size만큼의 작은 차원을 가진 결과를 낼 것이고, 이 결과를 GRU에 내보내게 된다.

     

    다음으로, Decoder 클래스를 살펴보자.

     

    class Decoder(nn.Module):
    	def __init__(self, hidden_size, output_size):
        	super(Decoder, self).__init__()
            self.hidden_size = hidden_size
            self.embedding = nn.Embedding(output_size, hidden_size)
            self.gru = nn.GRU(hidden_size, hidden_size)
            self.out = nn.Linear(hidden_size, output_size)
            self.softmax = nn.LogSoftmax(dim=1)
            
        def forward(self, x, hidden):
        	x = self.embedding(x).view(1, 1, -1)
            x, hidden = self.gru(x, hidden)
            x = self.softmax(self.out(x[0]))
            return x, hidden

     

    이제 클래스가 왜 이렇게 구성되는지 어느 정도 예측해볼 수 있을 것이다.

    Decoder는 Encoder의 결과물을 hidden state로 받아 출력을 낸다고 했다.

    여기서 'out' layer는 one-hot-vector 형태, 즉 index로 표현된 출력 결과물을 Target text에 사용되는 단어로 복원해준다.

     

    이러한 형태의 Encoder, Decoder는 가장 단순한 형태이다. 이 개념을 여러가지 방법으로 보완 및 발전시켜 최근에도 많이 쓰이는 우수한 모델이 많이 개발되었다.

    728x90
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기