본문 바로가기
Machine Learning/개념 정리

Forward Pass code의 수학적 이해와 code 구현하기

by W_log 2023. 9. 14.

오늘은 이전까지 배워온 내용을 실제로 Forward pass 코드로 구현하는 작업을 해보려고 한다. 캐글이 바로 데이터를 가져오기 쉽고 별도의 설정 없이도 바로 웹에서 돌아가기 때문에 캐글 노트북 기준으로 코드를 작성했다. 사용법은 이 블로그(링크)에서 잘 설명이 되어 있어서 참고하시면 될 것 같다.

 

실습 데이터 설명

1. MNIST 데이터란?

- MNIST 데이터는 사람의 손으로 직접 쓴 0~9까지의 글씨와 실제 해당 이미지가 무슨 숫자를 의미하는지를 라벨로 정의해놓은 데이터다.

- 이미지라고 표현했지만, 실제로는 784픽셀로 이루어진 이미지 중에 검은색으로 칠해진 경우에 1, 흰색으로 칠해진 경우에는 0으로 표시한 벡터이다. 

- 실제 데이터를 예시로 표현해보면 이렇게 구성되어 있다.(train data)

index label 1X1 1X2 ... 28X26 28X27 28X28
0 5 0 1 ... 1 1 0
1 0 1 0 ... 0 0 0
2 4 0 1 ... 0 0 1
... ... ... ... ... ... ... ...
59998 7 0 1 ... 0 0 1
59999 3 0 1 ... 1 1 0

- 딥러닝은 결국 학습 데이터를 숫자로 바꿔야하기 때문에 MNIST 역시도 이미지 파일을 1,0의 숫자 데이터로 바꾸는 작업을 거쳤다. 이러한 작업을 임베딩이라고 하는데, 어떻게 임베딩하냐에 대해서도 성능이 달라지기 때문에 추후에 논문을 볼 때에도 이런 것들을 잘 체크해보려고 한다.

 

수학 & 계산 수식 설명

- 우리가 만드려는 딥러닝 모델은 hidden layer가 각각 128개, 64개, 32개, 16개로 이루어진 모델로 정의해보았다. 여기에 있는 128,64, 32, 16은 임의로 내가 정의한 숫자이기 때문에 어떻게 설정해도 관계는 없다. 

- 우선 각 Layer들의 notation들을 정리해보면, 아래와 같다. 

기호(Notation) 의미 예시
a0 Input layer로 우리가 해결하려는 task의 요구사항이다. - MNIST의 숫자 벡터
a0(1) Input layer의 2번째 순서의 벡터를 의미한다. - 하단 그림에서 2번째 노란색 원
a1 ~ an Hidden layer로 기계에게 학습시키려는 layer이다. - 하단 그림에서 회색 원
Wn(i,j) (n-1)와 n 사이에 있는 가중치로,
a(n-1)의 i번째 성분에 가중치를 곱해서 a(n)의 j번째로 값을 전달해주는 역할 
- 하단 그림에서 빨간 선

 

그림(layer간 계산 정리)

- 각 레이어에 있는 원은 숫자이고, 원과 원을 잇는 선은 가중치를 의미하는데 이 가중치가 어떻게 작동하는지는 위 도식을 통해서 정리해놓았다.

- 결국 이전 레이어의 값을 다음 레이어에 있는 값로 얼마만큼 전달해줄지를 W를 곱해서 가져가기 때문에 이전 값에 가중을 해준다는 의미에서 가중치(weight)라고 부른다고 이해해도 좋을 것 같다.

 

 

벡터로 표현하기

- 각각을 다 하나하나 계산해도 좋지만, 편의를 위해 이것들을 모아서 행렬 형태로 곱하는 방식이 더 효율적이기 때문에 실제로 계산을 할때는 위와 같이 행렬로 표현해서 계산한다.

 

 

 

활성화 함수

- 상단의 그림을 보면, 3단계에서 Activation Function을 거치는데, 이는 모든 값을 그대로 전달하지 않고 뉴런의 역치처럼 특정 값 이상인 경우에만 전달하는 역할을 해준다.

- 활성화 함수에는 여러 종류가 있지만 이번에는 시그모이드 함수를 써보려고 한다.

 

 

 

필요한 코드

코드를 직접 작성하기에 앞서서, 어떤 것들이 필요한지에 대해서 정리하고 코드를 작성하는 편이다. 비교적 간단해서, 코드의 주석으로 설명을 대체한다.

 

정의할 필요가 있는 값들

1. 가중치의 초기값(weight initializer)

- 결국 우리가 학습시키려는 대상은 뉴런과 뉴런사이의 연산을 도와주는 Weight이다. 이는 기계가 여러 케이스들을 보면서 학습하기 때문에 인간이 조정할 수 없는 수치이다.

- 하지만 초기값이 있어야 학습을 시작할 수 있기 때문에 각 뉴런간의 갯수만큼 값을 지정해줘야한다.

- 어떤 값을 지정해줘야할까? 모든 것을 다 0으로 주게 되면 이후에 나올 학습과정에서 곱셈이 일어나는데 다 0이 곱해져버려서 학습을 하지 못할 수 있다.

- 결국 랜덤하게 값을 정해주도록 하기 위해 np.random.randn을 활용한다. 

#M,N 행렬로 만들어주고, 각 행렬의 값은 표준정규분포에 해당하는 숫자를 랜덤으로 가져와서 넣어준다.
np.random.randn(M,N)

 - 수학 & 계산 수식 설명처럼, 우리가 만들려는 신경망의 구조가 각 층별로 몇개로 이루어져있는지 정의하고 그에 맞게 np.random.randn을 사용해주면 된다.

 

2. 활성화 함수

- sigmoid라는 함수명으로 정의를 하고, array는 모두 브로드 캐스팅이 되기 때문에 input은 한개로만 지정해둔다.

 

3. forward 계산

- 위의 계산식에 맞게 코드를 작성해주면 되고, 보통은 shape함수를 활용해서 (K , M) X (M X N) 이 차수가 맞는지 확인하고 그에 맞게 순서를 지정해두는 것만 유의하면 된다.

 

 

 

code(실제 코드와 설명은 아래에서 볼 수 있다.)

###캐글 데이터셋에서 노트북 열기를 했을 때, 나오는 기본 설정
import numpy as np 
import pandas as pd 
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename)) #코드 실행시에 pd.read_csv를 통해 가져올 파일 경로를 알려준다.

###데이터셋 가져오기
mnist_test = pd.read_csv('/kaggle/input/mnist-in-csv/mnist_test.csv')
mnist_train = pd.read_csv('/kaggle/input/mnist-in-csv/mnist_train.csv')

###train data, test data 세팅
y_train = mnist_train['label']
x_train = mnist_train.loc[:,mnist_train.columns != 'label']
y_test = mnist_test['label']
x_test = mnist_test.loc[:,mnist_test.columns != 'label']


###array 차원 확인하기
print(y_train.shape) #(60000,)
print(x_train.shape) #(60000, 784)
print(y_test.shape)  #(10000,)
print(x_test.shape)  #(10000, 784)



###Weight layer의 초기 임의값 넣어주기
def weight_initializer(neuron_layer_structure):
    parameters = {}
    for i in range(len(neuron_layer_structure)-1):
        parameters['W' + str(i+1)] = np.random.randn(neuron_layer_structure[i],neuron_layer_structure[i+1])  #np.random.randn은 
        parameters['b' + str(i+1)] = np.random.randn(neuron_layer_structure[i+1])
    
    return parameters


param = weight_initializer([784,128,64,32,16,10]) #예시로 넣을 신경망 모델링
param['W1'].shape  #784X128


###활성화 함수 넣어주기
def sigmoid(x):
    return 1/(1+np.exp(-x))

def forward_pass(x, parameters):
    cache = {'a0': x}
    for i in range(1,len(parameters)//2+1):
        cache['z'+ str(i)] = cache['a' + str(i-1)] @ parameters['W' + str(i)] + parameters['b' + str(i)]
        cache['a'+ str(i)] = sigmoid(cache['z'+ str(i)])
    
    return cache

###실제로 한번 forward_pass 실행하기
cache_forward = forward_pass(x_train.loc[0,],param)
print(cache_forward['a1'].shape) #(128,)
print(cache_forward['a2'].shape) #(64,)
print(cache_forward['a3'].shape) #(32,)
print(cache_forward['a4'].shape) #(16,)
print(cache_forward['a5'].shape) #(10,)