본문 바로가기

implement_ml_models/MLP

implement_MLP(parameter_update)

MLP 모델에서 delta 값을 이용하여 각 노드들의 입력값의 변화에 따른 오차 함수의 변화량을 계산했고 

delta 값을 통해 각 가중치의 변화량에 따른 오차 함수의 변화량을 구할 수 있었다.

이렇게 구한 가중치 변화량에 따른 오차 함수의 변화량을 통해 파라미터를 업데이트할 수 있다.

각 가중치를 업데이트 하는데 간편하게 하기 위해서 

각 층에 해당하는 가중치의 값들을 튜플형태로 저장한다.

data = np.array(([1],[2],[3]))
data
>>>
array([[1],
       [2],
       [3]])

target = np.array(([0.5],[0.1]))
target
>>>
array([[0.5],
       [0.1]])
       
w1 = np.array(([0.1, 0.2], [0.3, 0.4], [0.5, 0.6]))
w2 = np.array(([0.7, 0.8], [0.9, 0.1]))

b1 = np.array(([0.5]))
b2 = np.array(([0.3]))

이전에는 numpy array 형태로 저장해서 사용, 각 가중치를 업데이트할 때 층에 해당하는 각기 다른 가중치 변수명(w1, w2)에 접근하여 업데이트하는 과정을 거쳐야 한다.

w1 = [[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]]
w2 = [[[0.7, 0.8], [0.9, 0.1]]]

w = w1.append(w2)
w
>>>
[[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]], [[[0.7, 0.8], [0.9, 0.1]]]]

3차원 배열의 층은 MLP 의 각 층에 해당한다.

w[0]
>>>
[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]

3차원 배열의 행은 해당 층의 각 노드들에 연결된 가중치들에 해당한다.

w[0][0]
>>>
[0.1, 0.2]

3차원 배열의 열은 노드간 열결되어 있는 각 가중치들에 해당한다.

w[0][0][0]
>>>
0.1

 

가중치 값들을 3차원 배열로 선언하면서 배열의 요소에 접근하여 파라미터 접근, 업데이트에 대해 일반화가 가능하다.

편향도 동일하게 3차원 배열로 선언해준다.

b = [[[0.5]],[[0.3]]]
b
>>>
[[[0.3]], [[0.3]]]

 

이러한 3차원 배열을 numpy array 로 변환할 때는 각 층당 노드의 개수가 다르므로 3차원 배열의 각 층에 접근한 다음 numpy array 로 변환해줘야 한다.

w_array = np.array(w[0])
w_array
>>>
array([[0.1, 0.2],
       [0.3, 0.4],
       [0.5, 0.6]])

해당 가중치와 이전 노드의 출력값과의 가중치 연산이 이뤄지므로 반복을 통해 각 가중치를 불러오고 연산을 수행할 수 있을 것

가중치 연산 - 활성화 함수 연산 의 두 과정을 반복해야 한다.

함수를 통해 이를 구현해본다.

class activation:
  def sigmoid(self, x):
    return 1 / (1+np.e**-x)
    
def forward_cal(input, w, b):
  activation1 = activation()
  hidden_output = input
  for i in range(len(w)):
    w_numpy_array = np.array(w[i])
    hidden_input = w_numpy_array.T @ hidden_output + b[i][0][0]
    hidden_output = activation1.sigmoid(hidden_input)

  return hidden_output

활성화 함수 클래스를 사용하여 3차원 배열의 각 가중치 층에 접근하여 MLP 연산을 수행,

hidden_out 의 값이 각 층의 노드들의 출력값이고, 마지막 출력층의 경우 예측값이 된다.

predict = forward_cal(data, w, b)
predict
>>>
array([[0.86103399],
       [0.75879129]])

 

오차 역전파 연산 역시 동일한 방법으로 수행이 가능,

델타값 역시 3차원 배열의 값으로 층에는 각 은닉층 노드의 변화량에 대한 오차 함수의 변화량을, 행과 열은 해당 층의 노드들, 하나의 노드 값에 해당한다.

def cal_delta(target, predict, w, b):
  activation1 = activation()
  delta = ((predict - target) * activation1.sigmoid_diff(predict).tolist())

  return delta
  
delta = cal_delta(target, predict, w, b)
delta
>>>
array([[0.04319933],
       [0.12057664]])

은닉층의 델타값을 계산하기 위해서는 해당 노드의 출력값을 알아야 하므로 이전의 순전파 과정의 각 층별 입, 출력값이 저장되어 있어야 한다.

def forward_cal(input, w, b):
  activation1 = activation()
  hidden_output = input
  hidden_output_array = []
  hidden_output_array.append(hidden_output)
  
  for i in range(len(w)):
    w_numpy_array = np.array(w[i])
    hidden_input = w_numpy_array.T @ hidden_output + b[i][0][0]
    hidden_output = activation1.sigmoid(hidden_input)
    hidden_output_array.append(hidden_output)

  predict = hidden_output

  return predict, hidden_output_array
  
predict, hidden_output_array = forward_cal(data, w, b)

hidden_output_array
>>>
[array([[1],
        [2],
        [3]]), 
 array([[0.93702664],
        [0.96442881]]), 
 array([[0.86103399],
        [0.75879129]])]

forward_cal 의 수정, 각 노드의 출력을 저장한다.

cal_delta 함수를 통해 각 노드별 변화에 대한 오차 함수의 변화량을 구할 수 있다.

가중치 변화량은 해당 delta 값에 가중치 변화량에 대한 노드 입력의 변화량, 즉 이전 노드 출력값을 곱해 구할 수 있다.

가중치 배열은 순서대로 입력층 - 은닉층 - 출력층 대로 저장되어 있지만, 계산의 편의를 위해 역전파 계산 전 해당 배열을 뒤집어준다.

w
>>>
[[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]], [[0.7, 0.8], [0.9, 0.1]]]

w = w[::-1]
w
>>>
[[[0.7, 0.8], [0.9, 0.1]], [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]]

hidden_output_array = hidden_output_array[::-1]
hidden_output_array
>>>
[array([[0.86103399],
        [0.75879129]]), array([[0.93702664],
        [0.96442881]]), array([[1],
        [2],
        [3]])]

각 층에 대한 delta 값 계산

def cal_delta(target, predict, w, b, hidden_output_array):
  activation1 = activation()
  delta = ((predict - target) * activation1.sigmoid_diff(predict).tolist())
  delta_array = []
  delta_array.append(delta.tolist())
  
  # 출력층 이전의 층들에 대한 delta 값을 계산한다.
  for i in range(len(w) - 1):
    delta = (np.array(delta_array[i]).T @ np.array(w[0]).T).T * activation1.sigmoid_diff(np.array(hidden_output_array[1+i]))
    delta_array.append(delta.tolist())
  return delta_array
  
delta_array = cal_delta(target, predict, w, b, hidden_output_array)
delta_array
>>>
[[[0.043199326918126744], [0.1205766380251159]],
 [[0.007476326694688966], [0.001747440588389323]]]

이 delta 값에 이전 층의 노드 출력과의 연산을 통해 가중치 변화량을 계산할 수 있다.

def cal_w_update(delta_array, hidden_output_array, b):
  w_diff = []
  b_diff = []
  for i in range(len(delta_array)):
    w_diff_list = (np.array(delta_array[i]) @ np.array(hidden_output_array[i+1]).T).tolist()
    w_diff.append(w_diff_list)

    b_diff_list = np.sum(np.array(delta_array[i]) * np.array(b[i]))
    b_diff.append(b_diff_list)
  
  return w_diff, b_diff
  
w_diff, b_diff = cal_w_update(delta_array, hidden_output_array, b)

w_diff
>>>
[[[0.040478920322688954, 0.04166267548387157],
  [0.11298352246660469, 0.11628758361206636]],
 [[0.007476326694688966, 0.014952653389377932, 0.0224289800840669],
  [0.001747440588389323, 0.003494881176778646, 0.005242321765167969]]]
  
  b_diff
  >>>
  [0.04913278948297279, 0.0046118836415391445]

각 가중치의 변화량에 대한 오차 함수의 변화량을 계산할 수 있다.

층의 순서대로 다시 정렬

w_diff = w_diff[::-1]
w_diff
>>>
[[[0.007476326694688966, 0.014952653389377932, 0.0224289800840669],
  [0.001747440588389323, 0.003494881176778646, 0.005242321765167969]],
 [[0.040478920322688954, 0.04166267548387157],
  [0.11298352246660469, 0.11628758361206636]]]
  
b_diff = b_diff[::-1]
b_diff
>>>
[0.0046118836415391445, 0.04913278948297279]

기존 가중치에 해당 변화량만큼의 업데이트를 통해 가중치 파라미터 업데이트를 수행한다.

 

 

'implement_ml_models > MLP' 카테고리의 다른 글

implement_MLP(parameter_update)(3)  (0) 2023.02.11
implement_MLP(parameter_update)(2)  (0) 2023.01.31
implement_DNN_biasupdate  (1) 2022.12.02
implement_DNN_generalization(1)  (0) 2022.12.02
implement_delta(delta_update)  (0) 2022.12.02