본문 바로가기
AI/자연어처리

08. RNN 기초

by 사라리24 2024. 6. 27.
SMALL



1. 순환 신경망(Recurrent Neural Network)

 

  • 시계열 또는 자연어와 같은 Sequence 데이터를 모델링하는데 강력한 신경망, 시계열 데이터나 시퀀스 데이터를 잘 처리
  • 예) 주식 가격, 텍스트 데이터, 오디오 데이터
  • Sequence: 단어의 문장, 연결되어 있는 정보

1. RNN 동작 방식

  • 은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 다시 출력층 방향으로 보내면서 은닉층 노드의 다음 계산의 입력으로 보내는 특징
  • 셀(cell): 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드, 이전의 값을 기억하려고 하는 일종의 메모리 역할을 수행
  • 은닉 상태( hidden state): 셀이 출력층 방향 또는 다음 시점인 t+1의 자신에게 보내는 값

      rnn = torch.nn.RNN
    (input_size, hidden_size)
      outputs, state = rnn(input_data)
       # state: hidden state

2. inupt size 

  • 단어가 입력되면 각 글자를 백터의 형태로 변환하여 ont-hot encoding 해주는 과정이 필요
  •  "hello"
    • h = [1, 0, 0, 0]
    • e = [0, 1, 0 , 0]
    • l = [0, 0, 1, 0]
    • o = [0, 0, 0, 1]
       - > input size를 4로 설정 
       - > input data의 세번째 차원으로 입력 
         (차원 축소된 이유는 중복된 단어 제외됨)

3. hidden state

  • hidden state의 size는 output shape의 세번째 차원
  • output size와 같음
  • 셀에서 연산된 결과를 두가지로 나눠, 하나는 output으로 출력되고, 다른 하나는 hidden state로 다음 step에 그대로 전해짐 

4. Sequence Length

  • 총 Sequence가 몇 개인지를 나타냄
  • "hello"
    • x0 = [1, 0, 0, 0]
    • x1 = [0, 1, 0 , 0]
    • x2 = [0, 0, 1, 0]
    • x3 = [0, 0, 1, 0]
    • x4 = [0, 0, 0, 1]
    • 'hello'를 입력으로 보내면 sequence length는 5
  •  파이토치에서는 모델이 sequence length를 알아서 파악하기 때문에 파라미터로 전달해 줄 필요가 없음 

5. Batch Size 

  • 여러 데이터를 묶어 하나의 batch로 만들어 학습을 진행
  •  [h, e, l, l, 0][e, o, l, l, l][l, l, e, e, l]처럼  h, e, l, o를 가지고 만들 수 있는 데이터 중 3개를 하나의 batch로 묶어 학습을 진행함
  • batch size를 모델에서 자동으로 파악하고 output data, input data에서의 첫번째 차원에 위치함 

 

 

간단한 RNN(Recurrent Neural Network)을 정의하고 입력 데이터를 처리


       
        import torch
        import numpy as np
        from torch.nn import RNN

        input_size = 4
        hidden_size = 2

        input_size = 4
        hidden_size = 2

        h = [1, 0, 0, 0]
        e = [0, 1, 0 , 0]
        l = [0, 0, 1, 0]
        o = [0, 0, 0, 1]

        input_data_np = np.array([[h, e, l, l, o],
                                  [e, o, l, l, l],
                                  [l, l, e, e, l]], dtype = np.float32)

        input_data = torch.Tensor(input_data_np)

        rnn = RNN(input_size, hidden_size)
        outputs, state = rnn(input_data)

       state

tensor([[[ 0.4268, -0.1465],
         [ 0.5059,  0.5654],
         [ 0.0742,  0.1633],
         [ 0.0742,  0.1633],
         [ 0.7979, -0.0312]]], grad_fn=<StackBackward0>)
  • 주석
더보기

       
      import torch
      import numpy as np
      from torch.nn import RNN

      input_size = 4  # 입력 데이터의 크기. 각 입력 벡터의 길이입니다.
      hidden_size = 2  # 은닉 상태의 크기. RNN 셀 내부의 은닉 상태 벡터의 크기입니다.

      # 입력 데이터를 one-hot encoding으로 정의합니다.
      h = [1, 0, 0, 0]  # 'h'를 나타내는 one-hot 벡터
      e = [0, 1, 0, 0]  # 'e'를 나타내는 one-hot 벡터
      l = [0, 0, 1, 0]  # 'l'을 나타내는 one-hot 벡터
      o = [0, 0, 0, 1]  # 'o'를 나타내는 one-hot 벡터

      # 입력 데이터를 numpy 배열로 정의합니다. shape는 (배치 크기, 시퀀스 길이, 입력 크기)입니다.
      input_data_np = np.array([[h, e, l, l, o],  # 첫 번째 시퀀스
                                                            [e, o, l, l, l],  # 두 번째 시퀀스
                                                            [l, l, e, e, l]], dtype=np.float32)  # 세 번째 시퀀스

      # numpy 배열을 PyTorch Tensor로 변환합니다.
      input_data = torch.Tensor(input_data_np)

      # RNN 모델을 정의합니다. 입력 크기와 은닉 상태 크기를 인자로 받습니다.
      rnn = RNN(input_size, hidden_size)

      # 입력 데이터를 RNN에 넣어 출력을 계산합니다. outputs와 status를 반환합니다.
      outputs, state = rnn(input_data)

      print("입력 데이터:", input_data)
      print("출력 데이터:", outputs)
      print("은닉 상태:", state)


입력 데이터: tensor([[[1., 0., 0., 0.],
         [0., 1., 0., 0.],
         [0., 0., 1., 0.],
         [0., 0., 1., 0.],
         [0., 0., 0., 1.]],

        [[0., 1., 0., 0.],
         [0., 0., 0., 1.],
         [0., 0., 1., 0.],
         [0., 0., 1., 0.],
         [0., 0., 1., 0.]],

        [[0., 0., 1., 0.],
         [0., 0., 1., 0.],
         [0., 1., 0., 0.],
         [0., 1., 0., 0.],
         [0., 0., 1., 0.]]])
출력 데이터: tensor([[[-0.5207,  0.9362],
         [-0.6398,  0.7532],
         [-0.5574,  0.8718],
         [-0.5574,  0.8718],
         [-0.1133,  0.7588]],

        [[-0.9055,  0.8389],
         [-0.6818,  0.8142],
         [-0.8767,  0.9132],
         [-0.8767,  0.9132],
         [-0.7872,  0.9210]],

        [[-0.9112,  0.8984],
         [-0.8855,  0.9052],
         [-0.9334,  0.8133],
         [-0.9334,  0.8133],
         [-0.9070,  0.9083]]], grad_fn=<StackBackward0>)
은닉 상태: tensor([[[-0.9112,  0.8984],
         [-0.8855,  0.9052],
         [-0.9334,  0.8133],
         [-0.9334,  0.8133],
         [-0.9070,  0.9083]]], grad_fn=<StackBackward0>)

 

 

 

test를 사용하여 고유한 문자 집합을 만들기
     각 문자를 인덱스로 매핑하는 사전을 생성하기
     이를 기반으로 입력 벡터와 은닉 상태 벡터의 크기를 설정




          test =
'hello! world'
          string_set = list(set(test))
          print(string_set)
      

          string_dic = {c: i for i, c in enumerate(string_set)}
          print(string_dic)


          input_size = len(string_dic)
          print(input_size)
          hidden_size = len(string_dic)
          print(hidden_size)



['!', 'l', 'd', 'e', 'o', 'r', 'w', 'h', ' ']

-------------------------------------------------------------

{'!': 0, 'l': 1, 'd': 2, 'e': 3, 'o': 4, 'r': 5, 'w': 6, 'h': 7, ' ': 8}

---------------------------------------------------------------

9
9

 

 

 

test라는 문자열을 string_dic이라는 사전에 기반하여 인덱스 리스트로 변환
    test_idx 리스트를 x_data로 변환


       
        test_idx = [string_dic[c] for c in test]
        print(test_idx)

        x_data = [test_idx[:]]
        print(x_data)

 
[7, 3, 1, 1, 4, 0, 8, 6, 4, 5, 1, 2]

----------------------------------------------

[[7, 3, 1, 1, 4, 0, 8, 6, 4, 5, 1, 2]]

 

 

◼' x_data'리스트의 각 요소를 one-hot 인코딩 형식으로 변환하여 'x_one_hot '리스트를 만들기


       
        x_one_hot = [np.eye(input_size)[x] for x in x_data]
        print(x_one_hot)


[array([[0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0.]])]

 

 

 

test_idx 리스트를 y_data로 복사하기


       
        y_data = [test_idx[:]]
        print(y_data)


[[7, 3, 1, 1, 4, 0, 8, 6, 4, 5, 1, 2]]



 

 

RNN 모델을 학습시키기


       

        X = torch.FloatTensor(x_one_hot)
        y = torch.LongTensor(y_data)

        rnn = RNN(input_size, hidden_size)
        loss_fun = torch.nn.CrossEntropyLoss()
        optimizer = torch.optim.Adam(rnn.parameters(), lr = 0.1)

        # outputs, state = rnn(X)
        # loss: x, predition: x(idx), predition_str: x(str)

        for i in range(100):
            optimizer.zero_grad()
            outputs, state = rnn(X)
            # (배치사이즈, 시퀀스길이, 히든사이즈)
            # outputs: (1, 12, 12) -> (12, 12)
            # y: (1, 12) -> (12,)
           
            loss = loss_fun(outputs.view(-1, input_size), y.view(-1))
            loss.backward()
            optimizer.step()
            result = outputs.data.numpy().argmax(axis=2)
            result_str = ''.join([string_set[ch] for ch in np.squeeze(result)])
            print(i, 'loss: ', loss)
            print('prediction: ', result, ' prediction_str: ', result_str)

 

 

 

RNN의 단점 

: 입력과 출력이 고정 
: 기울기 소실 
: 단점을 극복하기 위해 RNN의 발전 형태인 LSTM과 GRU를 사용(문제를 완벽히 해결하지 못함)

 

 

'AI > 자연어처리' 카테고리의 다른 글

10. CNN Text Classification  (0) 2024.07.02
09. CBOW Text Classification  (0) 2024.07.01
07. 워드 임베딩 시각화  (0) 2024.06.27
06. 자연어처리 - 워드 임베딩  (0) 2024.06.25
05. 자연어처리 - 임베딩 실습  (0) 2024.06.25