implement_ml_models/RNN

implement_RNN (vector-to-sequence) 오류 수정

명징직조지훈 2023. 3. 6. 01:18

 

2023.03.05 - [분류 전체보기] - implement_RNN (vector-to-sequence) 문제 해결

 

implement_RNN (vector-to-sequence) 문제 해결

2023.03.05 - [분류 전체보기] - implement_RNN (vector-to-sequence) 2개의 가중치, bias 값 추가 implement_RNN (vector-to-sequence) 2개의 가중치, bias 값 추가 2023.03.02 - [분류 전체보기] - implement_RNN (vector-to-sequence) 2개의

teach-meaning.tistory.com

RNN 계산 과정,

    #초기 학습, 임의의 가중치가 생성된다.
    def delta_to_sequence_cal_rnn_test(self, input, target):

      # 타겟값의 저장
      self.target = target

      # 입력 데이터의 저장
      self.input = input

      # 입력 데이터 크기에 맞는 가중치 값이 생성되어야 한다.
      # 입력 (3,1) 데이터에 대한 연산 결과 (1,1)를 예측한다고 가정,

      # 입력값에 사용되는 가중치
      # (3,1) 크기의 입력 데이터에 대해 (1,1) 의 행렬곱 연산 결과를 얻기 위해, (1,3) 크기의 가중치 행렬이 필요하다
      self.input_w = np.random.rand(target.shape[0], input.shape[1])
      
      # 이전 출력에 대한 가중치
      # (1,1) 노드 출력에 대해 (1,1) 의 행렬곱 연산 결과를 얻기 위한, (1,1) 크기의 가중치 행렬
      self.before_w = np.random.rand(target.shape[0], target.shape[0])

      # bias 값 생성, (1,1) 행렬에 더해지기 위한 (1,1) 크기의 bias 값 생성
      self.bias = np.random.rand(target.shape[0], target.shape[0])

      # 이전 노드의 출력, 첫 부분의 경우 이 값이 0이다. 행렬의 크기는 (1,1)
      before = np.zeros((target.shape[0], target.shape[0]))

      #self.node_output.append(before)
      
      # (3, n) 크기의 입력 데이터에서 n 번의 반복을 수행하게 됨
      for i in range(input.shape[0]):
        # 노드 입력 데이터의 numpy 행렬 생성
        input_data = input[i].reshape(input.shape[1],-1)

        # RNN 노드 입력 값 계산, 이전 노드 출력 @ 가중치 + 입력 데이터 @ 가중치
        node_input = (self.before_w @ before) + (self.input_w @ input_data) + self.bias
        self.node_input.append(node_input)

        # 노드 출력 계산, 활성화 함수 연산을 수행한다.
        node_output = self.activation.non(node_input)
        self.node_output.append(node_output)

        # 타입 스텝의 출력이 다음 노드의 입력이 된다.
        before = node_output

      self.predict = node_output

      self.cost_cal()

      return self.predict

1 : 학습에 입력값과 예측값이 입력된다

2 : 임의의 두 가중치, before_w, input_w 와 bias 가 입력 데이터 크기에 맞게 생성된다.

3 : 입력 데이터 크기에 맞게 0으로 초기화된 초기 노드 출력이 생성된다. 여기서 해당 입력은 node_input에 저장될 필요가 없으므로 삭제해준다.

4 : 입력 데이터 크기에 맞게 반복이 수행된다.

5 : 입력 데이터를 행렬곱을 위한 numpy 행렬로 수정해준다. 그 크기에 맞게 수정

6 : 활성화 함수 연산 전의 노드 입력값을 계산한다. 노드간 가중치 @ 이전 노드 출력 + 입력 가중치 @ 데이터 입력 + 편향

7 : 활성화 함수 연산 후의 노드 출력을 계산한다.

8 : 해당 노드 출력은 다음 노드의 입력이다.

9 : 5~8의 과정이 반복되고 마지막 노드의 출력이 예측값이 된다.

10 : 예측값을 통한 비용 함수값을 계산한다.

rnn.delta_to_sequence_cal_rnn_test(input, target)
>>>
(array([[1.57795929]]), 1.867960879028231)

print(rnn.input_w, rnn.before_w, rnn.bias)
>>>
[[0.53680354]] [[0.45702764]] [[0.84214604]]

계산한 예측값과 비용 함수값,

각 가중치와 편향값의 확인,

각 노드별 변화량, delta 값의 계산

    def cal_delta(self):
      self.delta = []
      #마지막 노드의 변화량에 대한 오차 함수의 변화량 계산
      #delta = (self.cost.diff_error_squared_sum() * self.activation.non_diff(self.predict))
      delta = (self.cost.diff_error_squared_sum() * self.activation.non_diff(self.node_output[len(self.node_output) - 1]))
      self.delta.append(delta)

      # n-1 개의 delta 값을 계산하기 위한 반복
      for i in range(input.shape[0] - 1, 0, -1):
        # 노드 출력의 변화에 대한 비용 함수의 변화량 계산
        delta = self.before_w * delta

        # 활성화 함수에 따른 변화량 계산, 해당 값이 노드 변화에 대한 비용 함수의 변화량이 된다.
        delta = delta * self.activation.non_diff(self.node_input[i])

        # 노드별 delta 값의 저장
        self.delta.append(delta)

1 : delta 배열의 초기화

2 : 초기 출력의 경우 노드 출력(예측값)에 대한 비용 함수의 미분 함수의 값 계산

비용 함수의 미분값, 예측값의 변화에 따른 비용 함수의 변화량이다.

활성화 함수의 미분값에는 활성화 함수 연산 전의 노드 입력값을 넣어준다. 하지만 값을 그대로 출력하는 활성화 함수는 이 미분값이 항상 1이므로 크게 상관은 없다.

3 : delta 값을 역전파 과정을 통해 추가한다.

4 : 

이전 노드 출력에 대한 노드 입력의 변화량은 before_w 의 값이다. 

해당 함수를 통해 입력 데이터 크기에 맞는 각 노드별 delta 값(노드입력 변화에 따른 비용함수의 변화량) 이 계산된다.

rnn.cal_delta()

rnn.delta
>>>
[1.932853268630721,
 array([[0.88336736]]),
 array([[0.4037233]]),
 array([[0.1845127]]),
 array([[0.08432741]]),
 array([[0.03853995]]),
 array([[0.01761382]]),
 array([[0.00805]]),
 array([[0.00367907]]),
 array([[0.00168144]]),
 array([[0.00076846]]),
 array([[0.00035121]]),
 array([[0.00016051]]),
 array([[7.33585774e-05]]),
 array([[3.35268972e-05]]),
 array([[1.53227186e-05]]),
 array([[7.00290587e-06]]),
 array([[3.20052152e-06]]),
 array([[1.46272678e-06]]),
 array([[6.68506565e-07]]),
 array([[3.05525975e-07]]),
 array([[1.39633814e-07]]),
 array([[6.38165121e-08]]),
 array([[2.91659097e-08]]),
 array([[1.33296268e-08]]),
 array([[6.09200782e-09]]),
 array([[2.78421594e-09]]),
 array([[1.27246363e-09]]),
 array([[5.81551045e-10]]),
 array([[2.65784899e-10]]),
 array([[1.21471044e-10]]),
 array([[5.55156243e-11]]),
 array([[2.53721746e-11]]),
 array([[1.1595785e-11]]),
 array([[5.2995942e-12]]),
 array([[2.42206101e-12]]),
 array([[1.10694882e-12]]),
 array([[5.05906203e-13]]),
 array([[2.31213116e-13]]),
 array([[1.05670784e-13]]),
 array([[4.82944686e-14]]),
 array([[2.20719069e-14]]),
 array([[1.00874714e-14]]),
 array([[4.61025322e-15]]),
 array([[2.10701313e-15]]),
 array([[9.62963232e-16]]),
 array([[4.4010081e-16]]),
 array([[2.01138233e-16]]),
 array([[9.19257313e-17]]),
 array([[4.20125997e-17]])]

delta 값은 마지막 노드부터 저장되는데 이전 델타값으로 갈수록, 값이 급격하게 감소하거나 증가하는데, 이는 before_w 의 반복된 곱셈 연산으로 인한 것이다.

 

delta 값을 계산하는 과정에서 실수가 있어 이를 수정

    def cal_delta(self):
      #마지막 노드의 변화량에 대한 오차 함수의 변화량 계산
      delta = (self.cost.diff_error_squared_sum() * self.activation.sigmoid_diff(self.predict))
      self.delta.append(delta)

      # n-1 개의 delta 값을 계산하기 위한 반복
      for i in range(input.shape[0] - 1, 0, -1):
        # 노드 출력의 변화에 대한 비용 함수의 변화량 계산
        delta = self.before_w @ delta

        # 활성화 함수에 따른 변화량 계산, 해당 값이 노드 변화에 대한 비용 함수의 변화량이 된다.
        # 
        delta = delta * self.activation.non_diff(self.node_input[i])

        # 노드별 delta 값의 저장
        self.delta.append(delta)

활성화 함수 미분값에 계산에 있어 입력값을 이전에는 node_output 값을 넣었는데 활성화 함수의 계산 값인 node_input 값이 들어가야 한다.

이후 가중치 업데이트 함수

    def update_weight(self, learning_rate):
      # 원활한 연산을 위해 마지막 노드부터 거꾸로 계산된 delta 값을 뒤집어준다.
      self.delta = self.delta[::-1]

      result = 0

      # 해당 delta 값과의 데이터 입력값을 통해 input_w 의 가중치 변화량을 계산할 수 있다
      for i in range(self.input.shape[0]):
        result = result + self.delta[i] * self.node_input[i]
        print(self.delta[i], self.node_input[i])

      self.before_weight_update = result

      result = 0
      # 이전 노드 출력값을 통해 before_w 의 가중치 변화량 계산
      for i in range(self.input.shape[0]):
        result = result + self.delta[i] * self.input[i]

      self.input_weight_update = result

      # bias_update 의 계산
      result = 0
      
      for i in range(self.input.shape[0]):
        result = result + self.delta[i]

      self.bias_update = result

      # 가중치 업데이트
      self.input_w = self.input_w - (self.input_weight_update * learning_rate)

      self.before_w = self.before_w - (self.before_weight_update * learning_rate)

      self.bias = self.bias + self.bias_update * learning_rate

1 : 먼저 거꾸로 저장된 delta 값을 뒤집어준다.

2 : 

각 가중치 변화량은

input_w_update = input * delta,

before_w_update = before * delta,

bias = 1 * delta 

값이 된다. 

rnn.update_weight(0.01)

print(rnn.input_weight_update, rnn.before_weight_update, rnn.bias_update)
>>>
[[6.0962773]] [[0.09709869]] [[3.55976362]]

임의의 가중치가 결정되고 학습을 반복하는 함수

    def iterations(self, iterations, learning_rate):
      # j 번의 학습
      for j in range(iterations):
        # 초기화
        self.node_input = []
        self.node_output = []
        self.delta = []

        # 이전 노드의 출력, 첫 부분의 경우 이 값이 0이다. 행렬의 크기는 (1,1)
        before = np.zeros((self.target.shape[0], self.target.shape[0]))

        for i in range(input.shape[0]):
          # 노드 입력 데이터의 numpy 행렬 생성
          input_data = input[i].reshape(input.shape[1],-1)

          # RNN 노드 입력 값 계산, 이전 노드 출력 @ 가중치 + 입력 데이터 @ 가중치 + bias
          # 업데이트된 가중치, 편향값으로 계산이 이뤄진다.
          node_input = (self.before_w @ before) + (self.input_w @ input_data) + self.bias
          self.node_input.append(node_input)

          # 노드 출력 계산, 활성화 함수 연산을 수행한다.
          node_output = self.activation.non(node_input)
          self.node_output.append(node_output)

          # 순환 노드의 다음 노드 입력값
          before = node_output

        # 매 학습마다 예측값이 달라짐
        self.predict = before

        # 예측값에 따른 비용 함숫값 계산
        self.cost_result = self.cost_cal()

        print(self.cost_cal())

        # 각 노드별 비용 함수의 변화량 계산
        self.cal_delta()

        # 계산한 delta 값을 통해 가중치 값 갱신
        self.update_weight(learning_rate)

1 : 노드 입력과 출력, delta 값을 초기화준다.

2 : 업데이트 된 가중치 값을 통해 학습

3 : 비용 함수 계산

4 : 가중치 업데이트

5 : 1~4의 과정을 정해진 학습 횟수, 학습률을 통해 학습한다.

rnn.iterations(100, 0.01)
>>>
1.73179085461459
1.6067607711006486
1.49179330634851
1.3859389478963788
1.2883573833162305
...
0.0036026754102895316
0.0033819166244338727
0.0031747166658001817
0.0029802398639591473
0.0027977023209275484
0.0026263686797589622

print(rnn.predict, rnn.target)
>>>
[[-0.28241821]] [[-0.35489398]]

학습을 반복하면서 target 값에 수렴하는 것을 확인할 수 있다.