이번에는 multiple Perceptron을 구현해보자
하나의 은닉계층을 갖는 다층 퍼셉트론 구현하기
📍 은닉층이 하나인 다층 신경망을 만들기 위한 설계도
파란색으로 표시된 새롭게 정의된 함수들에 대해서 살펴보자.
1. 파라미터 초기화 함수 init_model_hidden1() 정의
input_cnt, hidden_cnt, output_cnt를 받아 각 크기에 맞는 입력 계층과 은닉 계층 사이, 은닉 계층과 출력 계층 사이의 파라미터를 생성한다.
def init_model_hidden1():
global pm_output, pm_hidden, input_cnt, output_cnt, hidden_cnt
pm_hidden = allocate_param_pair([input_cnt,hidden_cnt]) # 입력층과 은닉층의 가중치와 편향 저장
pm_output = allocate_param_pair([hidden_cnt,output_cnt]) # 은닉층과 출력층의 가중치와 편향 저장
2. 파라미터 생성 함수 allocate_param_pair() 정의
np.random.normal()와 np.zeros()를 활용할 수 있다. 그리고 파라미터의 크기를 알려주기 위해 함수의 인자값을 받아와줄 수 있다.
이 과정에서 편향은 퍼셉트론 수와 일치시켜 주기 위해 shape[-1]이란 값을 통해 크기를 지정 해 줄 수 있게 된다.
def allocate_param_pair(shape):
weight = np.random.normal(RND_MEAN,RND_STD,shape)
#편향은 항상 출력 퍼셉트론의 크기와 동일하기에 [-1] 을 넣어 출력 크기에 맞춰 진행
bias = np.zeros(shape[-1]) # np.random.normal을 사용해도 무관하다
return {'w':weight, 'b':bias}
3. 신경망 연산 함수 forward_neuralnet_hidden1(x) 정의
pm_hidden, pm_output 을 전역변수로 받아준다. 이렇게 받은 변수에는 각 가중치와 편향이 들어 있기에 각 계층별 연산을 수행 그리고 여기서 구한 최종 결괏값인 output은 반환하여 돌려주며, 입력값 x 와 중간 계산 결과인 hidden 은 역전파 수행을 위해 리스트로 묶어 함께 반환한다.
def forward_neuralnet_hidden1(x):
global pm_output, pm_hidden
hidden = relu(np.matmul(x, pm_hidden['w']) + pm_hidden['b'])
output = np.matmul(hidden, pm_output['w']) + pm_output['b']
return output, [x, hidden]
4. 활성화 함수 relu() 정의
def relu(x):
return max(0, x)
4.1 활성화 함수 편미분 relu_derv() 정의
def relu_derv(y):
return np.sign(y)
은닉 계층의 활성화 함수인 𝑅𝑒𝐿𝑈의 역전파는 앞서 그래프로 살펴보았듯 음수인 경우의 기울기는 0, 양수인 경우의 기울기는 1, 그리고 0인 경우의 기울기는 0으로 출력해야 한다. np.sign()는 이러한 기능을 갖고 있으므로, 이외의 함수를 사용하지 않고도 𝑅𝑒𝐿𝑈 함수의 역전파 과정을 수행할 수 있게 된다.
5. 역전파 함수 backprop_neuralnet_hidden1() 정의
def backprop_neuralnet_hidden1(G_output, aux):
global pm_output, pm_hidden
x, hidden = aux
g_output_w_out = hidden.transpose() # 가중치를 갱신하는 과정에서 현상을 맞춰주기 위해
G_w_output = np.matmul(g_output_w_out, G_output)
G_b_output = np.sum(G_output, axis=0)
# DELTA k+1구하는 방법 1 - DELTA(k) * Wk
g_output_hidden = pm_output['w'].transpose()
G_hidden = np.matmul(G_output, g_output_hidden)
#출력 계층의 파라미터 갱신
pm_output['w'] -= LEARNING_RATE * G_w_output
pm_output['b'] -= LEARNING_RATE * G_b_output
# DELTA k+1구하는 방법 2 - G_hidden * f'(x_k)
G_hidden = G_hidden * relu_derv(hidden)
g_hidden_w_hid = x.transpose()
#은닉 계층의 파라미터 갱신
G_w_hid = np.matmul(g_hidden_w_hid, G_hidden)
G_b_hid = np.sum(G_hidden, axis=0)
pm_hidden['w'] -= LEARNING_RATE * G_w_hid
pm_hidden['b'] -= LEARNING_RATE * G_b_hid
첫 번째, 은닉 계층과 출력 계층 사이의 파라미터 갱신을 위한 $\frac{\partial L}{\partial W}$ 과 $\frac{\partial L}{\partial B}$ 을 먼저 구한다.
이미 마지막 계층의 $\delta k$ 를 알고 있기에, 이 값을 활용하여 마지막이 아닌 계층의 $\delta k + 1$ 을 먼저 구해주도록 한다.
(※ 이 값을 활용해 이전 계층의 파라미터 갱신을 수행할 수 있음 그리고 이 값에 활성화 함수의 미분을 곱해 완성)
이렇게 첫 번째와 두 번째 과정을 모두 마쳤다면 마지막 계층의 파라미터 갱신을 수행할 수 있게 된다.
입력 계층과 은닉 계층 사이의 파라미터 갱신을 위한 $\frac{\partial L}{\partial W}$ 과 $\frac{\partial L}{\partial B}$ 을 구해주고,
학습률을 통해 파라미터 갱신을 수행하게 되면 하나의 은닉 계층을 갖는 다층 퍼셉트론의 학습과정이 모두 마무리된다.
다수의 은닉계층을 갖는 다층 퍼셉트론 구현하기
📍 은닉층이 여러개인 다층 신경망을 만들기 위한 설계도
은닉 계층의 수와 폭을 신경망 설계자가 자유롭게 조절 가능하도록 함수를 조금 수정해보자
1. 은닉 계층 설정 함수 set_hidden() 정의
- 주요 기능은 은닉 계층의 수와 폭을 설정하여 값을 넘겨주는 역할
하나의 계층을 갖는다면 일반적으로 함수에 들어올 값은 정수 형태가 될 것이다.
그렇기에 isinstance() 라는 함수로 들어오는 정보가 정수 형태인 경우 그 값을 hidden_cnt로 넘겨주고, hidden_config 에는 None 값을 할당시켜 준다. 여기서 hidden_cnt는 은닉 계층의 수가 하나인 경우에 사용되는 변수이며, hidden_config는 은닉 계층의 수가 다수인 경우에 사용되는 변수로 각각 구분하여 사용될 것이다.
def set_hidden(info):
global hidden_cnt, hidden_config
#일반적인 하나의 은닉계층 이라면
if isinstance(info, int):
hidden_cnt = info
hidden_config = None
else:
#리스트값이라면 그대로 다층 퍼셉트론 수행
hidden_config = info
2. 은닉 계층의 설정값에 따라 사용되는 기능들을 정의한 함수 init_model() 정의
앞서 은닉 계층의 수가 ‘int’ 인 경우 hidden_config 는 None 값을 가졌다.
if hidden_config is not None: 은 의미 그대로 hidden_config값이 None 이 아닌 경우, 즉 다수의 은닉 계층이 들어왔을 때를 의미하기에 이에 맞는 init_model_hiddens() 라는 함수를 실행시켜 준다.
else: 이후에는 hidden_config 가 None 이라는 것 이기에 하나의 은닉 계층을 갖는 신경망을 의미하게 된다.
def init_model():
if hidden_config is not None:
print(f'은닉 계층 {len(hidden_config)}을 갖는 다층 퍼셉트론이 구축되었습니다.')
init_model_hiddens()
else:
print("하나의 은닉계층을 갖는 다층 퍼셉트론이 구축되었습니다.")
init_model_hidden1()
3. 신경망 실행 함수 forward_neuralnet(x) 정의
def forward_neuralnet(x):
if hidden_config is not None:
return forward_neuralnet_hiddens(x)
else:
return forward_neuralnet_hidden1(x)
def backprop_neuralnet(G_output, hiddens):
if hidden_config is not None:
return backprop_neuralnet_hiddens(G_output,hiddens)
else:
return backprop_neuralnet_hidden1(G_output,hiddens)
‘hidden_config’ 를 기준으로 이 값이 ‘None’ 값이 아닌 경우 다수의 은닉 계층을 위한 함수를 실행한다.
4. 다수의 은닉 계층 파라미터를 초기화 하는 init_model_hiddens() 정의
def init_model_hiddens():
global pm_output, pm_hiddens, input_cnt, output_cnt, hidden_config
pm_hiddens = []
prev_cnt = input_cnt
for hidden_cnt in hidden_config: # hidden_config리스트로 받아올 은닉계층의 수와 폭을 반복문으로
pm_hiddens.append(allocate_param_pair([prev_cnt, hidden_cnt])) # 파라미터 저장
prev_cnt = hidden_cnt # 은닉계층의 수로 갱신
pm_output = allocate_param_pair([prev_cnt, output_cnt])
다수의 은닉 계층 파라미터를 초기화 하는 init_model_hiddens의 경우 은닉 계층의 정보를 받아 파라미터를 생성해줘야 한다.
그래서 정보가 저장되어 있는 hidden_config 를 hidden_cnt 로 넘겨주며 앞서 정의한 allocate_param_pair() 에 인자값으로 전달한다.
이 과정에서 prev_cnt 는 처음에 input_cnt 로 설정한 값을 전달 받는다. 이렇게 되면 결과적으로 [input_cnt, hidden_cnt] 를 전달받게 된다.
그리고 이렇게 생성된 파라미터는 pm_hiddens 로 저장되며 다시 hidden_cnt 는 prev_cnt 로 값을 변경한다. 이러한 방법을 사용하는 이유는 신경망의 연산 과정을 떠올려 보면 알 수 있는데, 각 계층별 출력 크기에 위치한 값들이 다음 계층의 입력 크기로서 연산이 이뤄지기 때문이다. 그렇기에 변수를 재활용하여 코드를 작성할 수 있게 된다.
5. 가중치 갱신 함수 backprop_neuralnet_hiddens() 정의
def backprop_neuralnet_hiddens(G_output, aux):
global pm_output, pm_hiddens
hiddens = aux
g_output_w_out = hiddens[-1].transpose()
G_w_out = np.matmul(g_output_w_out, G_output)
G_b_out = np.sum(G_output, axis=0)
g_output_hidden = pm_output['w'].transpose()
G_hidden = np.matmul(G_output, g_output_hidden)
pm_output['w'] -= LEARNING_RATE * G_w_out
pm_output['b'] -= LEARNING_RATE * G_b_out
for n in reversed(range(len(pm_hiddens))):
G_hidden = G_hidden * relu_derv(hiddens[n + 1])
g_hidden_w_hid = hiddens[n].transpose()
G_w_hid = np.matmul(g_hidden_w_hid, G_hidden)
G_b_hid = np.sum(G_hidden, axis=0)
g_hidden_hidden = pm_hiddens[n]['w'].transpose()
G_hidden = np.matmul(G_hidden, g_hidden_hidden)
pm_hiddens[n]['w'] -= LEARNING_RATE * G_w_hid
pm_hiddens[n]['b'] -= LEARNING_RATE * G_b_hid
reversed() 라는 함수를 활용하여 pm_hiddens 에 저장되어 있는 은닉계층의 결과값 인덱스의 역으로 수행 마지막 은닉 계층의 결과값은 hiddens[-1]로 접근한다.
pm_hiddens 와 hiddens 의 크기가 정해지고, 이러한 크기에 맞게 반복문이 수행되며 다층 퍼셉트론의 가중치 갱신을 한다.
6. 신경망 연산 함수 forward_neuralnet_hiddens(x) 정의
def forward_neuralnet_hiddens(x):
global pm_output, pm_hiddens
hidden = x #독립변수들을 받아 초기화
hiddens = [x]
for pm_hidden in pm_hiddens:
#은닉계층 정보들(pm_hiddens)을 받아 pm_hidden으로 넘겨줍니다.
#은닉계층이 n개 존재
hidden = relu(np.matmul(hidden, pm_hidden['w']) + pm_hidden['b'])
#각 입력값에 대한 신경망 연산수행 후
#결괏값을 hidden으로 갱신
hiddens.append(hidden) #n번 갱신된 값은 n+1번째의 pm_hidden으로 다시 신경망 연산
#출력계층 정보를 받아 출력계층 신경망 연산 후 최종 갱신된 hidden을 활용하여 pm_output으로 다시 연산
#활성화 함수는 사용되지 않는다. (출력계층이기 때문에)
output = np.matmul(hidden, pm_output['w']) + pm_output['b']
return output, hiddens
살펴본 hiddens 을 구하는 과정이며 동시에 신경망 연산이 이뤄지는 과정
신경망 연산을 위해 초기 입력값을 hidden과 hiddens 로 받아주게 된다.
pm_hiddens에 저장된 계층별 파라미터들을 반복적으로 꺼내 신경망 연산과 활성화 함수를 적용하여 hidden 변수를 업데이트하고 여기에 저장된 값을 hiddens에 누적시켜 준다.
7. 실행하기
set_hidden([5,3,2])
main_exec()
set_hidden()를 통해 원하는 은닉 계층의 수와 폭을 설정하고 main_exec() 를 통해 신경망의 학습과정 및 테스트가 수행된다.
multi classification
'DL(Deep-Learning) > 개념' 카테고리의 다른 글
[실습] numpy로 만드는 단층 신경망 - 다중 분류2 (0) | 2022.01.29 |
---|---|
[실습] numpy로 만드는 단층 신경망 - 다중 분류1 (0) | 2022.01.29 |
[실습] numpy로 만드는 단층 신경망 - 이진 분류 2 (0) | 2022.01.29 |
[실습] numpy로 만드는 단층 신경망 - 이진 분류 1 (0) | 2022.01.29 |
train/test/validation 나누기 - splitfolders (0) | 2022.01.29 |