| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- 코사인 유사도
- BERTopic
- 옵티마이저
- 데이터넥스트레벨챌린지
- LDA
- 문맥을 반영한 토픽모델링
- 붕괴 스타레일
- 피파온라인 API
- 블루 아카이브
- 데벨챌
- 포아송분포
- Roberta
- 트위치
- 조축회
- 데이터리안
- 개체명 인식
- 구글 스토어 리뷰
- CTM
- 다항분포
- KeyBert
- geocoding
- SBERT
- NLP
- 원신
- Tableu
- 클래스 분류
- 토픽 모델링
- 블루아카이브 토픽모델링
- Optimizer
- 자연어 모델
- Today
- Total
분석하고싶은코코
딥러닝에 대한 이해와 순전파, 역전파 직접 구현 본문
딥러닝에 대해 이해하는 과정을 간략하게 정리하고 그 과정을 직접 구현해보았습니다.
페이지는 쥬피터 노트북을 활용해 작성하였습니다.
이 글은 안상준, 유원준 님의 딥 러닝을 이용한 자연어 처리 입문의 내용을 기반으로 작성하하였습니다.
순전파-역전파 직접 구현 해보자!¶
이번 페이지에서는 딥러닝의 인공 신경망(Neural Network)의 순전파와 역전파 직접 구현함으로서 인공 신경망 구조에 대해서 이해하는데 목적이 있습니다. 딥 러닝을 이해하기 위해서는 순전파와 역전파 말고도 다른 지식들이 필요하지만 이 페이지에서는 딥 러닝의 흐름을 파악하기 위해 순전파와 역전파 두 가지를 중점으로 다룹니다.
딥 러닝의 구조인 순전파와 역전파를 이해하기 이전에 딥 러닝이 무엇인가에 대해서 이해할 필요가 있습니다. 이를 위해서 딥 러닝의 기본인 퍼셉트론에 대해서 먼저 간단하게 짚고 넘어가겠습니다.
퍼셉트론(Perceptron)¶
퍼셉트론(Perceptron)은 프랑크 로젠블라트(Frank Rosenblatt)가 1957년에 제안한 초기 형태의 인공 신경망으로 다수의 입력으로부터 하나의 결과를 내보내는 알고리즘입니다.
위 그림과 같은 구조를 갖고 있습니다. y는 n개의 x값에 각각의 w값이 곱해진 값들과 가장 밑의 편향(bias)까지 더해서 나온 값입니다. 편향에도 역시 나름에 가중치를 부여하여 값을 만들어 낼 수 있습니다. 이 편향 역시 딥러닝에서 최적의 값을 찾아야할 변수 중 하나라고 보시면 됩니다. 이를 식으로 표현한다면 $$y = x_1w_1 + x_2w_2 + ... + x_nw_n + bias$$
와 같이 표현될 수 있겠죠. 이 인공신경망에는 활성화 함수라는게 존재합니다. 출력값인 y를 변경해주는 함수라고 보시면 됩니다. 기본적인 인공 뉴런의 활성화 함수는 계단 함수이지만 이를 시그모이드, 로지스틱 함수로 변경해준다면 이진분류가 가능한 뉴런으로 만들어 줄 수 있는 것입니다.
단층 퍼셉트론(Single-Layer Perceptron)¶
위 그림에서 봤던 방식이 단층 퍼셉트론입니다. 2개의 파트로 나뉘는데 이를 층(layer)라고 표현합니다. 단층 퍼셉트론은 층이 입력층과 출력층 2개의 층으로 이뤄진 모형입니다.
이 단층 퍼셉트론은 AND, OR, NAND 3가지에 구현이 가능합니다. 하지만 XOR은 구현하지 못했기 때문에 많이 부족한 인공 신경망이라는 이야기를 들었다고 합니다. 그 이유는 아래와 같습니다.
그림과 같이 하나의 직선으로는 XOR에 대한 표현이 어렵습니다. 이를 위해서 나온게 OR, NAND를 결합한 다층 퍼셉트론이 됩니다.
다층 퍼셉트론(MultiLayer Perceptron, MLP)¶

위 그림과 같이 입력층과 출력층 사이이에 층을 하나를 더 만들고 그 안에서 OR과 NAND를 처리하는 결과값을 넣어줍니다. 이 층을 은닉층(hidden layer)이라 합니다.. 그리고 거기서 나온 결과가 최종 출력층으로 정보를 보내게됩니다. 이렇게 한다면 XOR에 대한 표현이 가능하게 됩니다. 다층 퍼셉트론은 예시와 같이 1개의 은닉층이 있을 수 있고 무수히 많은 은닉층이 존재 할 수 있습니다. 이는 사용자가 자유롭게 설정할 수 있는 영역입니다.
그림과 같이 2개 이상의 은닉층이 존재할 경우 이를 심층 신경망(Deep Neural Network, DNN)라고 합니다. 이렇게 2개 이상의 은닉층을 사용하여 학습시키는 것을 딥 러닝(Deep Learning)이라 합니다.
순전파? 역전파?¶
순전파¶
순전파란 입력층부터 출력층까지 값들을 각 가중치를 적용해 앞으로 보내는 과정을 뜻합니다. 아래 그림과 같이 왼쪽부터 오른쪽까지 한 방향으로 값을 보내는 것을 의미합니다.
+순환 신경망(Recurrent Neural Network) 이번 페이지에서는 순전파와 역전파에 대해서 다루지만 또 다른 전달 방식인 순환 전달 방식이 있습니다. 순환 전달 방식은 앞으로도 값을 보내는 것 뿐 아니라 스스로 값을 다시 받거나 같은 층에 있는 다른 노드에게 보내는 방식입니다. 순환 신경망에 대한 방식은 아래 그림과 같습니다.
역전파¶
역전파는 순전파와 정 반대라고 생각하시면 됩니다. 이 역전파는 가중치를 변화시키는 기능을 합니다. 이는 최소의 오차를 만들어내는 가중치의 조합을 만들기 위한 목적 때문입니다. 따라서 출력층에서 입력층으로 값을 보내는데 그 값이 오차가 되는 것입니다. 왜냐하면 이 역전파의 목적은 오차를 최소로 하는 값을 찾는 것이기 때문에 가중치 변경의 기준이 오차여야하니 그 오차에 대한 정보를 보내는 것이죠.
그렇다면 이제 순전파와 역전파 과정을 거쳐 변화하는 가중치를 확인해보겠습니다.
그림과 같은 MLP(다층 퍼셉트론)이 있습니다. 파란색은 입력값, 빨간색은 가중치를 나타냅니다. 은닉층과 출력층의 노드에 가운데 선이 있는데 이는 활성화 함수를 적용하는 과정입니다. 예를들어 z1은 노드 가운데 선인 활성화함수를 지나서 h1이 된다는 뜻입니다. 이 예제에서는 모든 활성화 함수를 시그모이드 함수로 사용하였습니다. 그림에 쓰여진 정보로 계산해 보면 우리는 최종값(o1, o2)이 (0.60944600, 0.66484491)라는 것을 쉽게 알 수 있습니다. 이 과정이 순전파 과정입니다. 이 과정을 통해서 실제값 0.4와 0.6에 대한 오차가 나오게 됩니다. 딥 러닝에서는 이 오차를 처리하는 방법(loss)도 설정이 가능합니다. 여기서는 MSE(평균 제곱 오차)를 사용합니다. 그럼 o1, o2의 오차는 (0.02193381, 0.00203809)이고 전체 오차는 0.02397190이 됩니다. 이제 이 정보를 가지고 역전파를 하게 됩니다. 역전파 과정에 대해서 먼저 설명을 하고 자세하게 다뤄 보겠습니다.
역전파는 출력층에서부터 시작하니 우리가 업데이트 해줘야할 가중치는 4개(w5, w6, w7, w8)입니다. 가중치 업데이트를 하려면 가중치가 오차에 얼마나 영향을 주고 있는지를 알아야 합니다. 예를 들어 w5의 오차에 대한 영향력은 식으로 표현하면 $δE_total \over δw_5$ 이렇게 됩니다. 이를 4개를 각각 구해서 업데이트를 해줘야 하는 것이지요. (이 식은 미분의 연쇄 법칙으로 풀어서 풀이가 가능합니다. 자세한 내용이 궁금하다면 페이지 상단 링크를 들어가보시면 과정을 자세하게 이해하실 수 있습니다.)
그런데 업데이트 할 가중치라고 표현하는 것이 아니라 오차 그라디언트라는 용어로 이야기 합니다. 이 오차 그라디언트를 구하는 방법은 활성화 함수를 미분하는 것을 이해하면 쉽게 이해하실 수 있습니다. 오차 그라디언트를 구하기 위해서 활성화 함수를 미분하게 되는데 이 활성화 함수의 미분은 출력값의 변화하는 정도를 알기 위해서 하게 됩니다. (미분은 순간 기울기다!) 그런데 여기에 전체 오차를 곱해주면 해당 오차 대한 출력값의 영향력을 알 수 있게 됩니다. 그것이 오차 그라디언트가 되는 것입니다. 이 오차 그라디언트에 설정 해둔 학습률을 곱하여 기존 가중치를 업데이트 해주는 것입니다. 이 과정이 입력층까지 이어지게 됩니다. 이게 역전파 알고리즘의 전부입니다.
이제 이 부분을 직접 python 코드로 구현해보겠습니다.
직접 구현 - (1)¶
최상단의 링크를 보고 단순하게 구현했습니다. 이때는 오차 그라디언트에 대한 개념을 이해하지 못했기 때문에 하나하나 직접 구현했었습니다.
오차 그라디언트에 대한 이해 후 밑에 NeuralNetwork2로 작성하였습니다.
import numpy as np
import sympy as sy
# 시그모이드 함수
def sigmoid(activation):
return 1 / (1 + np.exp(-activation))
# 시그모이드 함수 미분
def sigmoid_derivative(output):
return output * (1 - output)
np.dot([[0.1, 0.2]], np.array([[0.3, 0.25], [0.4, 0.35]]).T)
# 해당 구현에서는 b(bias)편향은 고려하지 않았습니다.
class NeuralNetwork:
def __init__(self):
self.w1 = np.array([
[0.3, 0.25], #w1, w2
[0.4, 0.35] #w3, w4
] )
self.w2 = np.array([
[0.45, 0.4], #w5, w6
[0.7, 0.6] #w7, w8
])
def forward(self, X):
self.layer1 = np.dot(X, np.array(self.w1).T)
self.layer1_output = sigmoid(self.layer1)
# Hidden Layer outpus
self.layer2 = np.dot(self.layer1_output, np.array(self.w2).T)
self.output = sigmoid(self.layer2)
return self.output
def backPropagation(self, X, y, y_hat, learning_rate):
mse = np.sum((y-y_hat)**2)/2
# OUTPUT o1, o2의 평균 오차제곱 값
o1, o2 = sy.symbols('o1 o2')
E_o1 = 0.5 * ((0.4 - o1)**2)
E_o2 = 0.5 * ((0.6 - o2)**2)
total_E = 0.5 * ((0.4 - o1)**2) + 0.5 * ((0.6 - o2)**2)
# 순서대로 Error총합에 대한 o1,o2에 대한 미분, Error o1에 대한 o1 미분, Error o2에 대한 o2 미분
total_error_derivative_o1 = float(sy.diff(total_E, o1).subs({o1 : self.output[0][0]}))
total_error_derivative_o2 = float(sy.diff(total_E, o2).subs({o2 : self.output[0][1]}))
o1_error_derivative = float(sy.diff(E_o1, o1).subs({o1 : self.output[0][0]}))
o2_error_derivative = float(sy.diff(E_o2, o2).subs({o2 : self.output[0][1]}))
# 최종 레이어로 오는 가중치 조정
# 밑에서도 w1 업데이트 처리 중 w2값이 필요해 w1이 끝나면 업데이트
self.dW2 = np.array([[total_error_derivative_o1], [total_error_derivative_o2]]) *\
np.dot(sigmoid_derivative(self.output).T, self.layer1_output)
# 전체 에러에 대한 히든 노드 결과값의 미분
total_error_derivative_h1 = np.sum(np.array([o1_error_derivative, o2_error_derivative]) *\
sigmoid_derivative(self.output) *\
np.array([self.w2[0][0], self.w2[1][0]]))
total_error_derivative_h2 = np.sum(np.array([o1_error_derivative, o2_error_derivative]) *\
sigmoid_derivative(self.output) *\
np.array([self.w2[0][1], self.w2[1][1]]))
# 히든 레이어로 오는 가중치 조정
self.dW1 = np.array([[total_error_derivative_h1], [total_error_derivative_h2]]) *\
np.dot(sigmoid_derivative(self.layer1_output).T, np.array(X))
# sigmoid_derivative(self.layer1_output) * \
# X
self.w2 -= (self.dW2 * learning_rate)
self.w1 -= (self.dW1 * learning_rate)
# 모델 훈련
def train(self, X, y, learning_rate=0.5, epochs=1):
for i in range(epochs):
print("stat ",i)
# 순전파
y_hat = self.forward(X)
# 역전파
self.backPropagation(X, y, y_hat, learning_rate)
# 손실 계산
loss = np.mean((y_hat - y) ** 2)
print(f"Epoch {i}, Loss: {loss}")
return self
nn = NeuralNetwork()
X = [[0.1, 0.2]]
y = np.array([0.4, 0.6])
nn.train(X, y)
nn.output
stat 0 Epoch 0, Loss: 0.023971900751730026
array([[0.609446 , 0.66384491]])
직접 구현 - (2)¶
오차 그라디언트에 대한 개념 이해 후 직접 미분 식을 만들어서 쓰지 않았습니다.
오차 그라디언트에 대해서 이해했다면 역전파 과정을 위 코드와 비교해 보시면 쉽게 이해하실 수 있습니다.
import numpy as np
# 시그모이드 함수
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 시그모이드 함수 미분
def sigmoid_derivative(x):
return x * (1 - x)
# 신경망 클래스
class NeuralNetwork2:
def __init__(self):
# 가중치 초기화
self.w1 = np.array([[0.3, 0.25], [0.4, 0.35]])
self.w2 = np.array([[0.45, 0.4], [0.7, 0.6]])
def forward(self, X):
# 순전파 계산
self.layer1 = np.dot(X, self.w1.T)
self.layer1_output = sigmoid(self.layer1)
self.layer2 = np.dot(self.layer1_output, self.w2.T)
self.output = sigmoid(self.layer2)
return self.output
def backPropagation(self, X, y, y_hat, learning_rate):
# 출력 오차 계산
dE_dO = y_hat - y
# 미분 계산
dO_dZ2 = sigmoid_derivative(self.output)
dZ2_dW2 = self.layer1_output
dE_dW2 = np.outer(dE_dO * dO_dZ2, dZ2_dW2)
dZ2_dO1 = self.w2
dO1_dZ1 = sigmoid_derivative(self.layer1_output)
dZ1_dW1 = X
dE_dW1 = np.outer(dE_dO * dO_dZ2 * dZ2_dO1[:, 0] * dO1_dZ1[:, 0], dZ1_dW1) + \
np.outer(dE_dO * dO_dZ2 * dZ2_dO1[:, 1] * dO1_dZ1[:, 1], dZ1_dW1)
# 가중치 업데이트
self.w2 -= (learning_rate * dE_dW2)
self.w1 -= (learning_rate * dE_dW1)
def train(self, X, y, learning_rate=0.5, epochs=1):
for i in range(epochs):
# 순전파
y_hat = self.forward(X)
# 역전파
self.backPropagation(X, y, y_hat, learning_rate)
# 손실 계산
loss = np.mean((y_hat - y) ** 2)
print(f"Epoch {i}, Loss: {loss}")
return self
nn2 = NeuralNetwork2()
nn2.train(X, y)
nn2.output
Epoch 0, Loss: 0.023971900751730026
array([[0.609446 , 0.66384491]])
'머신러닝&딥러닝' 카테고리의 다른 글
| QLoRA-Efficient Finetuning of Quantized LLMs (1) | 2024.01.24 |
|---|---|
| Pytorch - Error 정리 페이지 (0) | 2024.01.18 |
| 기울기(Gradient) - 소실과 폭주(Vanishing & Exploding) (0) | 2023.12.14 |
| Tensorflow GPU사용하기 No colab (0) | 2023.09.13 |
| Optimizer - OGD, SGD, RLS (0) | 2023.03.26 |