역 전파 알고리즘 소스 | 오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다 14111 명이 이 답변을 좋아했습니다

당신은 주제를 찾고 있습니까 “역 전파 알고리즘 소스 – 오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다“? 다음 카테고리의 웹사이트 https://you.charoenmotorcycles.com 에서 귀하의 모든 질문에 답변해 드립니다: https://you.charoenmotorcycles.com/blog. 바로 아래에서 답을 찾을 수 있습니다. 작성자 테디노트 TeddyNote 이(가) 작성한 기사에는 조회수 15,176회 및 좋아요 345개 개의 좋아요가 있습니다.

Table of Contents

역 전파 알고리즘 소스 주제에 대한 동영상 보기

여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!

d여기에서 오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다 – 역 전파 알고리즘 소스 주제에 대한 세부정보를 참조하세요

오차역전파 (Backpropagation)에 대한 수학적 이해를 도와드리는 영상입니다.
편미분, Chain Rule에 대한 개념이 부족하신 분들은 이전 영상을 먼저 보시고 오세요.
#오차역전파 #딥러닝 #설명

텐서플로우 자격증 취득 강의: https://bit.ly/tfcert-vod
테디노트(깃헙 블로그) : https://teddylee777.github.io
머신러닝 혼자서 스터디 : https://github.com/teddylee777/machine-learning

역 전파 알고리즘 소스 주제에 대한 자세한 내용은 여기를 참조하세요.

[Deep Learning-딥러닝]Backpropagation (역전파) 및 Delta …

소스 구성은 델타룰 알고리즘 및 제곱합을 Python으로 구현한 소스와 두 번째는 Tensorflow를 이용한 구현 두 가지입니다. 선행 학습으로 “경사하강법”, “ …

+ 더 읽기

Source: ynebula.tistory.com

Date Published: 5/11/2022

View: 2364

C++::BackPropagation (역전파) – 홍귀찬

만약 어떤 뉴런의 input x값과 output y 값을 알고 잇을때 어떤 W(가중치) 와 b(바이어스)를 넣어야지 원하는 y값에 도달하게 할수잇을까? w와 b에 …

+ 여기에 더 보기

Source: redbinalgorithm.tistory.com

Date Published: 2/20/2021

View: 1815

[밑시딥] 오직! Numpy로 오차역전파를 사용한 신경망 학습 구현 …

먼저 활성화 함수들에 대한 넘파이 소스코드이다. import numpy as np # 1.sigmo def sigmo(x: np …

+ 여기를 클릭

Source: techblog-history-younghunjo1.tistory.com

Date Published: 11/24/2022

View: 6357

04-3) 역전파(BackPropagation) 이해하기 – 딥 러닝을 이용한 …

인공 신경망이 순전파 과정을 진행하여 예측값과 실제값의 오차를 계산하였을 때 어떻게 역전파 과정에서 경사 하강법을 사용하여 가중치를 업데이트하는지 직접 계산 …

+ 여기에 자세히 보기

Source: wikidocs.net

Date Published: 12/12/2021

View: 4834

3.14.1. 순전파(forward propagation) – Dive into Deep Learning

역전파(back-propagation)을 이용하는 경우 자동으로 그래디언트(gradient)를 계산하는 함수를 이용함으로 딥러닝 학습 알고리즘 구현이 굉장히 간단해졌습니다.

+ 여기에 자세히 보기

Source: ko.d2l.ai

Date Published: 12/9/2021

View: 8716

[35편] 딥러닝의 핵심 개념 – 역전파(backpropagation) 이해하기1

심층 신경망을 학습하는데 유용하게 활용되는 역전파(backpropagtion) 알고리즘도 결국 이 경사하강법을 이용합니다. 그러면 역전파가 무엇인지 그 …

+ 여기에 표시

Source: m.blog.naver.com

Date Published: 4/14/2021

View: 6011

역전파, 직접 짜봐야 하나요?

텐서플로우 같은 라이브러리가 역전파 알고리즘을 모두 자동으로 처리해 … 시그모이드 함수의 예에서 역전파가 전혀 이루어지지 않고 그래디언트가 …

+ 더 읽기

Source: tensorflow.blog

Date Published: 1/1/2022

View: 9889

[C#/COMMON] 신경망 역전파 알고리즘 사용하기

[C#/COMMON] 신경망 역전파 알고리즘 사용하기 … totalWeightCount) { throw new Exception(“잘못된 소스 가중치 배열 길이 입니다.

+ 여기에 더 보기

Source: icodebroker.tistory.com

Date Published: 4/16/2022

View: 4664

주제와 관련된 이미지 역 전파 알고리즘 소스

주제와 관련된 더 많은 사진을 참조하십시오 오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.

오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다
오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다

주제에 대한 기사 평가 역 전파 알고리즘 소스

  • Author: 테디노트 TeddyNote
  • Views: 조회수 15,176회
  • Likes: 좋아요 345개
  • Date Published: 2020. 11. 29.
  • Video Url link: https://www.youtube.com/watch?v=1Q_etC_GHHk

[Deep Learning-딥러닝]Backpropagation (역전파) 및 Delta Rule을 이용한 가중치 조정 방법 소스 구현

이번 포스팅은 MLP 구조를 이용하여 XOR 연산법을 구현하는 방법에 대해서 알아보겠습니다.

요새 좋은 라이브러리도 많아서 굳이 이런 걸 만들 필요가 있을까도 생각이 들겠지만, Tensorflow에서 제공하는 방법으로 구현은 당연하고, 학습 알고리즘을 더욱 잘 이해하려면 직접 구현이 꼭 필요하다고 생각이 들어서 직접 구현해 봤습니다.

파이썬을 다루어 본 적이 없어 Doc 사이트 및 많은 구글링을 하였습니다. 또한 국내 많은 개발자는 대부분 DB를 이용하여 데이터를 처리하는 업무를 많이 합니다. 저 역시 이런 일을 많이 했습니다. 행렬 계산이 머릿속에 잘 그려지지 않아 많은 시행착오가 있었습니다.

소스 구성은 델타룰 알고리즘 및 제곱합을 Python으로 구현한 소스와 두 번째는 Tensorflow를 이용한 구현 두 가지입니다.

선행 학습으로 “경사하강법”, “Solving XOR Problem with MLP”과 “Back propagation” 이론이 필요하므로 “https://ynebula.tistory.com/14”, “https://ynebula.tistory.com/22”, “https://ynebula.tistory.com/24” 포스팅 참고 바랍니다.

학습 내용 및 신경망 구성

XOR 연산을 제곱합 비용함수와 델타룰 학습 알고리즘으로 구현합니다. 신경망 구성은 다음과 같습니다.

Network

직접 구현에서는 구현 편의상 편차는 0으로 가정하고 구현하였습니다.

제곱합 및 델타룰 알고리즘 구현

소스 구성은 “Sigmoid 함수”, “제곱합 학습 알고리즘”, “가중치 초기화 및 학습 훈련”, “학습 테스트” 부분으로 구성되어 있습니다. 학습 방법은 SGD 방식으로 구현했습니다. 또한 행렬곱 연산과 행렬합 연산 방법을 이해해야 소스 원리를 알 수 있습니다.

1. Sigmoid 구현

Source-1

Sigmoid 함수를 구현하기 위해 numpy 라이브러리를 import 합니다. 개인적으로 학습률은 너무 높지 않은 수로 설정하는 것이 좋다고 생각됩니다. 과거에 학습률을 0.9로 했을 때 결과가 잘 나오지 않았던 기억이 있습니다. N은 데이터가 4개로 되어 있으므로 4로 설정합니다.

2. 제곱합 학습 알고리즘

Source-2

backpropagationXOR 함수는 인공신경망의 가중치를 조정해서 반환하는 기능을 합니다. 인자로 가중치와 학습 데이터를 넘겨받아 새로 갱신된 가중치를 반환합니다. 인자의 역할은 다음과 같습니다.

– W1: 입력층-은닉층 가중치 행렬을 보관하는 변수 – W2: 은닉층-출력층 가중치 행렬을 보관하는 변수 – X: 학습 데이터의 입력 데이터 – D: 학습 데이터의 정답 데이터

Feed forward 부분은 가중 합 및 활성함수를 적용하였습니다. 오차 부분에서 주의할 점은 은닉층의 오차를 구할 때 W(가중치)를 전치(transpose) 했다는 점입니다. 이 오차를 사용해 은닉층의 델타를 구합니다. SGD 방식을 사용했으므로 데이터를 학습할 때마다 가중치를 갱신합니다.

Source-3

학습이 완료되면 최종 W1, W2을 이용해서 훈련 같은 방법으로 출력 값을 구하면 됩니다.

Source-4

텐서플로

은닉층, 출력층의 가중 합을 구하기 위해 행렬 곱을 연산하는 tf.matmul 함수를 사용합니다. 행렬 연산이 끝나면 편차 더해줍니다. 그리고 활성함수 tf.sigmoid를 적용하여 출력값을 계산합니다.

reduce_mean 함수는 텐서의 차원을 1차원으로 줄이고 elements의 평균을 계산합니다. 즉 정답과 출력값의 차이인 오차의 평균을 반환합니다. GradientDescentOptimizer 함수는 경사하강법 알고리즘을 구현한 함수입니다. 이 함수는 Cost가 최소가 되도록 합니다. GradientDescentOptimizer 함수는 compute_gradient와 apply_gradients 함수를 결합한 형태입니다.

텐서플로는 Session() 함수를 통해 세션을 생성하고 프로그램을 Tensorflow 라이브러리와 연결이 됩니다. 즉, 텐서플로가 알고리즘을 실행하기 위해서는 Session을 생성하고 run 메서드에 train 매개변수를 넣어 호출해야 합니다. 또한 앞에서 선언한 변수를 initialize_all_variable() 메서드를 사용해서 먼저 초기화해야 합니다. 텐서의 자료형, 구조 및 이름을 매개변수로 설정합니다. feed_dict() 의 매개변수로 전달합니다.

Source

다음 git URL에서 전체 소스를 다운받을 수 있습니다.

https://github.com/ynebula/First-Contact-with-Books/blob/master/Deep_Learning/Backpropagation.ipynb

감사합니다.

[Reference]

딥러닝 첫걸음

www.tensorflow.org

C++::BackPropagation (역전파)

반응형

만약 어떤 뉴런의 input x값과 output y 값을 알고 잇을때 어떤 W(가중치) 와 b(바이어스)를 넣어야지 원하는 y값에 도달하게 할수잇을까? w와 b에 임의값을 주어 원하는값을 찾는다는것은 거의 불가능 하다. 이때 필요한것이 backpropagation(역전파)라고 한다.

E는 오류 함수라고 하는데 구하자고 하는 값을 Y target 이라 하고 현재 Y의 차이의 제곱을 2로 나눈값으로 정의 하고있다.

E 와 W의 관계에 대한 그래프이다. 오류가 가장 적어지게 하는 W값이 어느 구간에 존재할텐데 그 구간을 미분을 통해서 알수가 있다. 오류가 작아지는 방향으로 W를 갱신 하다보면 원하는 값에 도달할수 있다. 이때 알파 값은 접근하는 속도인데 그 값이 너무 크다면 오류값의 계산에 오류가 생길 것이고 너무 작다면 W값을 찾는데 너무 많은 시간이 걸릴 것이다. 이두개의 가장 적절한 값을 배치 하는것이 중요하다. 지금 그래프에서는 미분값이 양수 이므로 반대 방향으로 가야하기 때문에 현재 가중치에 – 를 하는것이다.

(그래프가 이차 곡선을 이루므로 양수일때는 기울기가 0인 방향에 오른쪽에 있기 때문이다.)

이값은 어떻게 구할까??

가중치는 시그마에 영향을 미치고 시그마는 함수에 함수는 y(출력값)에 y는 오류 값에 영향을 미친다.

당연히 체인룰을 통해서 간단하게 구할수가 있다.

b(bias)값도 이와 같은 형식으로 우리는 구할수가 있다.

이제는 C++로 구현해보도록 하겠다.

class Neuron { public: double w_; double b_; double input_; double output_; // saved for back-prop Neuron(); Neuron(const double& w_input,const double& b_input); double getAct(const double& x); double feedForward(const double& _input); double getActGrad(const double& x); void propBacward(const double& target); };

뉴런의 클래스 구조이다. 클래스 내의 각각의 함수를 살펴보자!(생성함수 제외)

double Neuron::feedForward(const double& _input) // 시그마와 함수 과정 { input_ = _input; const double sigma = w_ * input_ +b_; output_ = getAct(sigma); return output_; }

feedForward 라는 함수는 입력에 값이 들어오면 sigma 값을 계산하고 활성화 함수를 이용해서 바로 y값을 린턴하는 함수이다.

double Neuron::getActGrad(const double& x) { // linear or idenity activation fucntion return 1.0; }

getActGrad 는 getAct라는 함수의 미분값을 반환하는 함수이다.

여기선 1을 반환한다.

노란색 과정이다. f(sigma)= sigma 인데 일차적인 관계 이므로 이값은 1

void Neuron::propBacward(const double& target) { const double alpha = 0.1; // lerning rate const double grad = (output_ – target) * getActGrad(output_); w_ -= alpha * grad * input_; // last input_) came from d(wx+b)/dw =x b_ -= alpha * grad * 1.0; // last 1.0 came from d(wx+b)/db = 1 }

propBacward 역전파 과정이다. 내가 목표로 하는 타겟을 찾는 과정에서 w값과 b값을 조정한다.

여기서 알파는 0.1로 고정 했다.

위쪽에 설명한 식을 구하기 위해서 라운드E/라운드Y값을 알수있다.

(output_ – target)이 되고 라운드Y/라운드F 값은 1인것은 생각해보면 알수있다.

(출력값 자체가 어떤 함수 f라는 것의 통과해야만 나오는 결과이므로 1:1관계임)

getActGrad는 라운드f/라운드 sigma 값이다.

이값은 input_값으로 할수있다. 왜냐 sigma 는 (wx+b) 값인데 x는 입력값이다. 미분해보면 input값만남는다.

b_의 경우에는 미분하면 1이기에 1.0을 곱한다.

int main() { Neuron My_neuron(2.0,1.0); for(int i=0;i<100;i++) { cout<<"Trainin "<[밑시딥] 오직! Numpy로 오차역전파를 사용한 신경망 학습 구현하기

🔊 해당 포스팅은 밑바닥부터 시작하는 딥러닝 1권의 교재 내용을 기반으로 딥러닝 신경망을 Tensorflow, Pytorch와 같은 딥러닝 프레임워크를 사용하지 않고 순수한 Numpy로 구현하면서 딥러닝의 기초를 탄탄히 하고자 하는 목적 하에 게시되는 포스팅입니다. 내용은 주로 필자가 중요하다고 생각되는 내용 위주로 작성되었음을 알려드립니다.

밑바닥부터 시작하는 딥러닝

저번 포스팅에서 행렬 곱을 연산하는 계층과 활성화 함수가 적용된 계층의 역전파 방법까지 알아보면서 신경망 학습의 오차역전파 방법을 모두 이해해보았다. 이번 포스팅에서는 그동안 배운 내용들을 기반으로 오직 넘파이를 활용한 오차역전파 신경망 학습을 구현해보자.

먼저 복습 차원에서 활성화 함수와 손실 함수를 넘파이로 구현하는 소스코드를 보고 가자.

1. 활성화 함수(Sigmoid, Relu) 와 손실 함수(Cross-entropy Error)

먼저 활성화 함수들에 대한 넘파이 소스코드이다.

import numpy as np # 1.sigmoid def sigmoid(x: np.array): return 1 / (1 + np.exp(-x)) # 2.relu def relu(x: np.array): return np.maximum(0, x) # 3.softmax def softmax(x: np.array): if x.ndim == 2: x = x.T x = x – np.max(x, axis=0) y = np.exp(x) / np.sum(np.exp(x), axis=0) return y.T x = x – np.max(x) return np.exp(x) / np.sum(np.exp(x))

다음은 CEE(Cross-Entropy Error) 손실함수에 대한 넘파이 소스코드이다.

# 4.cross-entropy-error def cross_entropy_error(y: np.array, t: np.array): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1, y.size) # 레이블(t)가 원-핫 형태라면 레이블 형태로 변환 if t.size == y.size: t = t.argmax(aixs=1) batch_size = y.shape[0] return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

그리고 추후에 알아볼 오차역전파의 기울기 검증을 위해서 오차역전파 방법과 다르게 파라미터의 변화량값들인 기울기를 구하는 또 다른 방법으로서 수치 미분을 계산하는 소스코드도 보고 가자.

# 5. 수치 미분 계산 함수 def numerical_gradient(f, x: np.array): h = 1e-4 grads = np.zeros_like(x) it = np.nditer(x, flags=[‘multi_index’], op_flags=[‘readwrite’]) while not it.finished: idx = it.multi_index tmp_val = x[idx] # f(x+h) x[idx] = tmp_val + h fx1 = f(x) # f(x-h) x[idx] = tmp_val – h fx2 = f(x) grads[idx] = (fx1 – fx2) / (2*h) x[idx] = tmp_val it.iternext() return grads

2. 활성화 함수(Relu, Sigmoid) 계층

이제 활성화 함수 각 종류에 맞게 순전파, 역전파를 수행하는 계층 클래스를 만들자. 먼저 Relu 함수에 대한 소스코드이다.

# 1.Relu 계층 class Relu: def __init__(self): self.mask = None def forward(self, x: np.array): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx 다음은 Sigmoid 계층 소스코드이다. # 2.Sigmoid 계층 class Sigmoid: def __init__(self): self.y = None def forward(self, x: np.array): y = sigmoid(x) self.y = y return y def backward(self, dout): dx = dout * self.y * (1 - self.y) return dx 3. 행렬 곱(Affine) 계층 이번에는 행렬 곱 연산을 의미하는 Affine 계층을 넘파이로 구현하는 소스코드이다. # 3.Affine 계층 class Affine: def __init__(self, W, b): self.W = W self.b = b self.x = None self.original_x_shape = None self.dW = None self.db = None def forward(self, x: np.array): self.original_x_shape = x.shape x = x.reshape(x.shape[0], -1) self.x = x y = np.matmul(self.x, self.W) + self.b return y def backward(self, dout): dx = np.matmul(dout, self.W.T) self.dW = np.matmul(self.x.T, dout) self.db = np.sum(dout, axis=0) dx = dx.reshape(*self.original_x_shape) return dx 4. Softmax-with-Loss 계층 이번엔 Softmax와 Loss(여기서는 Cross-Entropy Error)를 하나의 계층으로 하는 계층을 넘파이로 구현하는 소스코드이다. # 4.Softmax-with-Loss 계층 class SoftmaxWithLoss: def __init__(self): self.loss = None # for loss 계층 self.t = None self.y = None def forward(self, x: np.array, t: np.array): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size return dx 이제 오차역전파를 수행할 때 필요한 활성함수, 손실함수, 그리고 각 활성함수와 손실함수에 맞는 계층 클래스들도 알아보았다. 이를 기반으로 2층 신경망 클래스를 만들어보자. 참고로 OrderedDict라는 순서가 있는 딕셔너리 객체를 호출했는데, 이는 딕셔너리에 추가한 순서를 기억하는 특징 때문이다. 이를 활용해서 순전파 때 호출한 레이어 순서를 역전파 시 뒤바꾸어서 호출할 수 있기 때문이다. import numpy as np from collections import OrderedDict class TwoLayerNet: def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01): # 2층 신경망의 파라미터 딕셔너리 self.params = {} self.params['W1'] = np.random.randn(input_size, hidden_size) * weight_init_std self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = np.random.randn(hidden_size, output_size) * weight_init_std self.params['b2'] = np.zeros(output_size) # 2층 신경망의 계층 생성 self.layers = OrderedDict() self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1']) self.layers['Relu1'] = Relu() self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2']) self.lastlayer = SoftmaxWithLoss() def predict(self, x): for layer in self.layers.values: x = layer.forward(x) return x def loss(self, x, t): y = self.predict(x) loss = self.lastlayer.forward(y, t) return loss def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) if t.ndim != 1: t = np.argmax(t, axis=1) acc = np.sum(y == t) / float(y.shape[0]) return acc def gradient(self, x, t): # 순전파 수행 self.loss(x, t) # 역전파 수행 - 1.Softmax-with-Loss 계층 dout = 1 dout = self.lastlayer.backward(dout) # 역전파 수행 - 2.나머지 계층 layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) # 역전파 수행한 결과의 파라미터 변화량 보관 grads = {} grads['W1'] = self.layers['Affine1'].dW grads['b1'] = self.layers['Affine1'].db grads['W2'] = self.layers['Affine2'].dW grads['b2'] = self.layers['Affine2'].db return grads # 수치미분으로 기울기 계산(for 오차역전파 기울기 검증 목적) def numerical_gradient(self, x, t): loss_w = lambda w: self.loss(x, t) grads = {} # 여기의 numerical_gradient 함수는 바깥에서 정의한 수치미분 계산 함수임! grads['W1'] = numerical_gradient(loss_w, self.params['W1']) grads['b1'] = numerical_gradient(loss_w, self.params['b1']) grads['W2'] = numerical_gradient(loss_w, self.params['W2']) grads['b2'] = numerical_gradient(loss_w, self.params['b2']) return grads 위 2층 신경망 클래스를 가지고 MNIST 데이터를 학습시켜보자. import numpy as np from dataset.mnist import load_mnist (X_train, y_train), (X_test, y_test) = load_mnist(normalize=True, one_hot_label=True) # 2층 신경망 설계 network = TwoLayerNet(input_size=28*28, hidden_size=50, output_size=10) steps = 1000 train_size = X_train.shape[0] batch_size= 100 learning_rate = 0.1 train_loss = [] train_acc = [] test_acc = [] # Mini-batch로 학습 for i in range(steps): batch_mask = np.random.choice(train_size, batch_size) X_batch = X_train[batch_mask] y_batch = y_train[batch_mask] # 오차역전파로 학습 수행 grads = network.gradient(X_batch, y_batch) # SGD로 경사하강법 수행 for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grads[key] # SGD로 파라미터 갱신 후 다시 Loss값 얻기 loss = network.loss(X_batch, y_batch) train_loss.append(loss) # 성능 중간 체크 if i % 10 == 0: tr_acc = network.accuracy(X_batch, y_batch) te_acc = network.accuracy(X_test, y_test) train_acc.append(tr_acc) test_acc.append(te_acc) print(f'{i+1}번째 학습 후 Train Acc:', round(tr_acc, 3)) print(f'{i+1}번째 학습 후 Test Acc:', round(te_acc, 3)) print() 다음은 오차역전파를 통해서 구한 기울기 값이 정말 잘 구해졌는지 검증하기 위한 방법으로 수치 미분을 활용할 수 있다. 수치 미분은 상대적으로 오차역전파보다 계산이 오래걸린다고 했다. 하지만 직접 수학적인 계산을 했기 때문에 해석적 방법을 사용하는 오차역전파 결과를 검증하는 데 자주 사용된다. 이를 기울기 확인(Gradient Check) 과정이라고도 한다. 각자 2가지 방법을 활용해 기울기를 구한 값 차이를 확인해보는 소스코드이다. 두 값 차이가 0에 가깝다면 오차역전파를 통한 기울기 계산이 잘 되었다고 할 수 있다. from dataset.mnist import load_mnist # load data (X_train, y_train), (X_test, y_test) = load_mnist(normalize=True, one_hot_label=True) # model network = TwoLayerNet(input_size=28*28, hidden_size=50, output_size=10) # Batch X_batch = X_train[:3] y_batch = y_train[:3] # 수치미분 grad_numerical = network.numerical_gradient(X_batch, y_batch) grad_propagation = network.gradient(X_batch, y_batch) for key in grad_numerical.keys(): diff = np.mean(np.abs(grad_numerical[key] - grad_propagation[key])) print('key:', key, 'diff:', diff)

04-3) 역전파(BackPropagation) 이해하기

인공 신경망이 순전파 과정을 진행하여 예측값과 실제값의 오차를 계산하였을 때 어떻게 역전파 과정에서 경사 하강법을 사용하여 가중치를 업데이트하는지 직접 계산을 통해 이해해봅시다.

1. 인공 신경망의 이해(Neural Network Overview)

우선 예제를 위해 사용될 인공 신경망을 소개합니다. 역전파의 이해를 위해서 여기서 사용할 인공 신경망은 입력층, 은닉층, 출력층 이렇게 3개의 층을 가집니다. 또한 해당 인공 신경망은 두 개의 입력과, 두 개의 은닉층 뉴런, 두 개의 출력층 뉴런을 사용합니다. 은닉층과 출력층의 모든 뉴런은 활성화 함수로 시그모이드 함수를 사용합니다.

위의 그림은 여기서 사용할 인공 신경망의 모습을 보여줍니다. 은닉층과 출력층의 모든 뉴런에서 변수 $z$가 존재하는데 여기서 변수 $z$는 이전층의 모든 입력이 각각의 가중치와 곱해진 값들이 모두 더해진 가중합을 의미합니다. 이 값은 뉴런에서 아직 시그모이드 함수를 거치지 않은 상태입니다. 즉, 활성화 함수의 입력을 의미합니다. $z$ 우측의 |를 지나서 존재하는 변수 $h$ 또는 $o$는 $z$가 시그모이드 함수를 지난 후의 값으로 각 뉴런의 출력값을 의미합니다. 이번 역전파 예제에서는 인공 신경망에 존재하는 모든 가중치 $w$에 대해서 역전파를 통해 업데이트하는 것을 목표로합니다. 해당 인공 신경망은 편향 $b$는 고려하지 않습니다.

2. 순전파(Forward Propagation)

주어진 값이 위의 그림과 같을 때 순전파를 진행해봅시다. 위의 그림에서 소수점 앞의 0은 생략하였습니다. 예를 들어 .25는 0.25를 의미합니다. 파란색 숫자는 입력값을 의미하며, 빨간색 숫자는 각 가중치의 값을 의미합니다. 앞으로 진행하는 계산의 결과값은 소수점 아래 여덟번째 자리까지 반올림하여 표기합니다.

각 입력은 입력층에서 은닉층 방향으로 향하면서 각 입력에 해당하는 가중치와 곱해지고, 결과적으로 가중합으로 계산되어 은닉층 뉴런의 시그모이드 함수의 입력값이 됩니다. $z_{1}$과 $z_{2}$는 시그모이드 함수의 입력으로 사용되는 각각의 값에 해당됩니다.

$$z_{1}=w_{1}x_{1} + w_{2}x_{2}=0.3 \text{×} 0.1 + 0.25 \text{×} 0.2= 0.08$$ $$z_{2}=w_{3}x_{1} + w_{4}x_{2}=0.4 \text{×} 0.1 + 0.35 \text{×} 0.2= 0.11$$ $z_{1}$과 $z_{2}$는 각각의 은닉층 뉴런에서 시그모이드 함수를 지나게 되는데 시그모이드 함수가 리턴하는 결과값은 은닉층 뉴런의 최종 출력값입니다. 식에서는 각각 $h_{1}$과 $h_{2}$에 해당되며, 아래의 결과와 같습니다. $$h_{1}=sigmoid(z_{1}) = 0.51998934$$ $$h_{2}=sigmoid(z_{2}) = 0.52747230$$ $h_{1}$과 $h_{2}$ 이 두 값은 다시 출력층의 뉴런으로 향하게 되는데 이때 다시 각각의 값에 해당되는 가중치와 곱해지고, 다시 가중합 되어 출력층 뉴런의 시그모이드 함수의 입력값이 됩니다. 식에서는 각각 $z_{3}$과 $z_{4}$에 해당됩니다. $$z_{3}=w_{5}h_{1}+w_{6}h_{2} = 0.45 \text{×} h_{1} + 0.4 \text{×} h_{2} = 0.44498412$$ $$z_{4}=w_{7}h_{1}+w_{8}h_{2} = 0.7 \text{×} h_{1} + 0.6 \text{×} h_{2} = 0.68047592$$ $z_{3}$과 $z_{4}$이 출력층 뉴런에서 시그모이드 함수를 지난 값은 이 인공 신경망이 최종적으로 계산한 출력값입니다. 실제값을 예측하기 위한 값으로서 예측값이라고도 부릅니다. $$o_{1}=sigmoid(z_{3})=0.60944600$$ $$o_{2}=sigmoid(z_{4})=0.66384491$$ 이제 해야할 일은 예측값과 실제값의 오차를 계산하기 위한 오차 함수를 선택하는 것입니다. 오차(Error)를 계산하기 위한 손실 함수(Loss function)로는 평균 제곱 오차 MSE를 사용합니다. 식에서는 실제값을 target이라고 표현하였으며, 순전파를 통해 나온 예측값을 output으로 표현하였습니다. 그리고 각 오차를 모두 더하면 전체 오차 $E_{total}$가 됩니다. $$E_{o1}=\frac{1}{2}(target_{o1}-output_{o1})^{2}=0.02193381$$ $$E_{o2}=\frac{1}{2}(target_{o2}-output_{o2})^{2}=0.00203809$$ $$E_{total}=E_{o1}+E_{o2}=0.02397190$$

3. 역전파 1단계(BackPropagation Step 1)

순전파가 입력층에서 출력층으로 향한다면 역전파는 반대로 출력층에서 입력층 방향으로 계산하면서 가중치를 업데이트해갑니다. 출력층 바로 이전의 은닉층을 N층이라고 하였을 때, 출력층과 N층 사이의 가중치를 업데이트하는 단계를 역전파 1단계, 그리고 N층과 N층의 이전층 사이의 가중치를 업데이트 하는 단계를 역전파 2단계라고 해봅시다.

역전파 1단계에서 업데이트 해야 할 가중치는 $w_{5}, w_{6}, w_{7}, w_{8}$ 총 4개입니다. 원리 자체는 동일하므로 우선 $w_{5}$에 대해서 먼저 업데이트를 진행해보겠습니다. 경사 하강법을 수행하려면 가중치 $w_{5}$를 업데이트 하기 위해서 $\frac{∂E_{total}}{∂w_{5}}$를 계산해야 합니다.

$\frac{∂E_{total}}{∂w_{5}}$를 계산하기 위해 미분의 연쇄 법칙(Chain rule)에 따라서 이와 같이 풀어 쓸 수 있습니다. $$\frac{∂E_{total}}{∂w_{5}} = \frac{∂E_{total}}{∂o_{1}} \text{×} \frac{∂o_{1}}{∂z_{3}} \text{×} \frac{∂z_{3}}{∂w_{5}}$$ 위의 식에서 우변의 세 개의 각 항에 대해서 순서대로 계산해봅시다. 우선 첫번째 항에 대해서 계산해보겠습니다. 미분을 진행하기 전에 $E_{total}$의 값을 상기해봅시다. $E_{total}$은 앞서 순전파를 진행하고 계산했던 전체 오차값입니다. 식은 다음과 같습니다. $$E_{total}=\frac{1}{2}(target_{o1}-output_{o1})^{2} + \frac{1}{2}(target_{o2}-output_{o2})^{2}$$ 이에 $\frac{∂E_{total}}{∂o_{1}}$는 다음과 같습니다. $$\frac{∂E_{total}}{∂o_{1}}=2 \text{×} \frac{1}{2}(target_{o1}-output_{o1})^{2-1} \text{×} (-1) + 0$$ $$\frac{∂E_{total}}{∂o_{1}}=-(target_{o1}-output_{o1})=-(0.4-0.60944600)=0.20944600$$

이제 두번째 항을 주목해봅시다. $o_{1}$이라는 값은 시그모이드 함수의 출력값입니다. 그런데 시그모이드 함수의 미분은 $f(x) \text{×} (1-f(x))$입니다. 앞으로의 계산 과정에서도 계속해서 시그모이드 함수를 미분해야 하는 상황이 생기므로 기억해둡시다. 이에 따라서 두번째 항의 미분 결과는 다음과 같습니다.

(시그모이드 함수 미분 참고 링크 : https://en.wikipedia.org/wiki/Logistic_function#Derivative)

$$\frac{∂o_{1}}{∂z_{3}}=o_{1}\text{×}(1-o_{1})=0.60944600(1-0.60944600)=0.23802157$$ 마지막으로 세번째 항은 $h_{1}$의 값과 동일합니다. $$\frac{∂z_{3}}{∂w_{5}}=h_{1}=0.51998934$$ 우변의 모든 항을 계산하였습니다. 이제 이 값을 모두 곱해주면 됩니다.

$$\frac{∂E_{total}}{∂w_{5}} = 0.20944600 \text{×} 0.23802157 \text{×} 0.51998934 = 0.02592286$$ 이제 앞서 배웠던 경사 하강법을 통해 가중치를 업데이트 할 때가 왔습니다! 하이퍼파라미터에 해당되는 학습률(learning rate) $α$는 0.5라고 가정합니다.

$$w_{5}^{+}=w_{5}-α\frac{∂E_{total}}{∂w_{5}}=0.45- 0.5 \text{×} 0.02592286=0.43703857$$ 이와 같은 원리로 $w_{6}^{+},\ w_{7}^{+},\ w_{8}^{+}$을 계산할 수 있습니다.

$$\frac{∂E_{total}}{∂w_{6}} = \frac{∂E_{total}}{∂o_{1}} \text{×} \frac{∂o_{1}}{∂z_{3}} \text{×} \frac{∂z_{3}}{∂w_{6}} → w_{6}^{+}=0.38685205$$ $$\frac{∂E_{total}}{∂w_{7}} = \frac{∂E_{total}}{∂o_{2}} \text{×} \frac{∂o_{2}}{∂z_{4}} \text{×} \frac{∂z_{4}}{∂w_{7}} → w_{7}^{+}=0.69629578$$ $$\frac{∂E_{total}}{∂w_{8}} = \frac{∂E_{total}}{∂o_{2}} \text{×} \frac{∂o_{2}}{∂z_{4}} \text{×} \frac{∂z_{4}}{∂w_{8}} → w_{8}^{+}=0.59624247$$

4. 역전파 2단계(BackPropagation Step 2)

1단계를 완료하였다면 이제 입력층 방향으로 이동하며 다시 계산을 이어갑니다. 위의 그림에서 빨간색 화살표는 순전파의 정반대 방향인 역전파의 방향을 보여줍니다. 현재 인공 신경망은 은닉층이 1개밖에 없으므로 이번 단계가 마지막 단계입니다. 하지만 은닉층이 더 많은 경우라면 입력층 방향으로 한 단계씩 계속해서 계산해가야 합니다.

이번 단계에서 계산할 가중치는 $w_{1}, w_{2}, w_{3}, w_{4}$입니다. 원리 자체는 동일하므로 우선 $w_{1}$에 대해서 먼저 업데이트를 진행해보겠습니다. 경사 하강법을 수행하려면 가중치 $w_{1}$를 업데이트 하기 위해서 $\frac{∂E_{total}}{∂w_{1}}$를 계산해야 합니다.

$\frac{∂E_{total}}{∂w_{1}}$를 계산하기 위해 미분의 연쇄 법칙(Chain rule)에 따라서 이와 같이 풀어 쓸 수 있습니다. $$\frac{∂E_{total}}{∂w_{1}} = \frac{∂E_{total}}{∂h_{1}} \text{×} \frac{∂h_{1}}{∂z_{1}} \text{×} \frac{∂z_{1}}{∂w_{1}}$$ 위의 식에서 우변의 첫번째항인 $\frac{∂E_{total}}{∂h_{1}}$는 다음과 같이 다시 식을 풀어서 쓸 수 있습니다.

$$\frac{∂E_{total}}{∂h_{1}} = \frac{∂E_{o1}}{∂h_{1}} + \frac{∂E_{o2}}{∂h_{1}}$$ 위의 식의 우변의 두 항을 각각 구해봅시다. 우선 첫번째 항 $\frac{∂E_{o1}}{∂h_{1}}$에 대해서 항을 분해 및 계산해보겠습니다.

$$\frac{∂E_{o1}}{∂h_{1}} = \frac{∂E_{o1}}{∂z_{3}} \text{×} \frac{{∂z_{3}}}{∂h_{1}} = \frac{∂E_{o1}}{∂o_{1}} \text{×} \frac{∂o_{1}}{∂z_{3}} \text{×} \frac{{∂z_{3}}}{∂h_{1}}$$ $$= -(target_{o1}-output_{o1}) \text{×} o_{1}\text{×}(1-o_{1}) \text{×} w_{5}$$ $$= 0.20944600 \text{×} 0.23802157 \text{×} 0.45 = 0.02243370$$ 이와 같은 원리로 $\frac{∂E_{o2}}{∂h_{1}}$ 또한 구합니다. $$\frac{∂E_{o2}}{∂h_{1}} = \frac{∂E_{o2}}{∂z_{4}} \text{×} \frac{{∂z_{4}}}{∂h_{1}} = \frac{∂E_{o2}}{∂o_{2}} \text{×} \frac{∂o_{2}}{∂z_{4}} \text{×} \frac{{∂z_{4}}}{∂h_{1}} = 0.00997311$$

$$\frac{∂E_{total}}{∂h_{1}} = 0.02243370 + 0.00997311 = 0.03240681$$ 이제 $\frac{∂E_{total}}{∂w_{1}}$를 구하기 위해서 필요한 첫번째 항을 구했습니다. 나머지 두 항에 대해서 구해보도록 하겠습니다. $$\frac{∂h_{1}}{∂z_{1}} = h_{1}\text{×}(1-h_{1}) = 0.51998934(1-0.51998934)=0.24960043$$ $$\frac{∂z_{1}}{∂w_{1}} = x_{1} = 0.1$$ 즉, $\frac{∂E_{total}}{∂w_{1}}$는 다음과 같습니다. $$\frac{∂E_{total}}{∂w_{1}} = 0.03240681 \text{×} 0.24960043 \text{×} 0.1 = 0.00080888$$ 이제 앞서 배웠던 경사 하강법을 통해 가중치를 업데이트 할 수 있습니다. $$w_{1}^{+}=w_{1}-α\frac{∂E_{total}}{∂w_{1}}=0.3- 0.5 \text{×} 0.00080888=0.29959556$$ 이와 같은 원리로 $w_{2}^{+},\ w_{3}^{+},\ w_{4}^{+}$을 계산할 수 있습니다.

$$\frac{∂E_{total}}{∂w_{2}} = \frac{∂E_{total}}{∂h_{1}} \text{×} \frac{∂h_{1}}{∂z_{1}} \text{×} \frac{∂z_{1}}{∂w_{2}} → w_{2}^{+}=0.24919112$$ $$\frac{∂E_{total}}{∂w_{3}} = \frac{∂E_{total}}{∂h_{2}} \text{×} \frac{∂h_{2}}{∂z_{2}} \text{×} \frac{∂z_{2}}{∂w_{3}} → w_{3}^{+}=0.39964496$$ $$\frac{∂E_{total}}{∂w_{4}} = \frac{∂E_{total}}{∂h_{2}} \text{×} \frac{∂h_{2}}{∂z_{2}} \text{×} \frac{∂z_{2}}{∂w_{4}} → w_{4}^{+}=0.34928991$$

5. 결과 확인

업데이트 된 가중치에 대해서 다시 한 번 순전파를 진행하여 오차가 감소하였는지 확인해보겠습니다.

$$z_{1}=w_{1}x_{1} + w_{2}x_{2}=0.29959556 \text{×} 0.1 + 0.24919112 \text{×} 0.2= 0.07979778$$ $$z_{2}=w_{3}x_{1} + w_{4}x_{2}=0.39964496 \text{×} 0.1 + 0.34928991 \text{×} 0.2= 0.10982248$$ $$h_{1}=sigmoid(z_{1}) = 0.51993887$$ $$h_{2}=sigmoid(z_{2}) = 0.52742806$$ $$z_{3}=w_{5}h_{1}+w_{6}h_{2} = 0.43703857 \text{×} h_{1} + 0.38685205 \text{×} h_{2} = 0.43126996$$ $$z_{4}=w_{7}h_{1}+w_{8}h_{2} = 0.69629578 \text{×} h_{1} + 0.59624247 \text{×} h_{2} = 0.67650625$$ $$o_{1}=sigmoid(z_{3})=0.60617688$$ $$o_{2}=sigmoid(z_{4})=0.66295848$$ $$E_{o1}=\frac{1}{2}(target_{o1}-output_{o1})^{2}=0.02125445$$ $$E_{o2}=\frac{1}{2}(target_{o2}-output_{o2})^{2}=0.00198189$$ $$E_{total}=E_{o1}+E_{o2}=0.02323634$$ 기존의 전체 오차 $E_{total}$가 0.02397190였으므로 1번의 역전파로 오차가 감소한 것을 확인할 수 있습니다. 인공 신경망의 학습은 오차를 최소화하는 가중치를 찾는 목적으로 순전파와 역전파를 반복하는 것을 말합니다.

https://medium.com/@14prakash/back-propagation-is-very-simple-who-made-it-complicated-97b794c97e5c

https://www.youtube.com/watch?v=ZMgax46Rd3g

3.14. 순전파(forward propagation), 역전파(back propagation), 연산 그래프 — Dive into Deep Learning documentation

3.14. 순전파(forward propagation), 역전파(back propagation), 연산 그래프¶

앞에서 우리는 모델을 학습 시키는 방법으로 미니 배치 확률적 경사 강하법(stochastic gradient descent) 최적화 알고리즘을 사용했습니다. 이를 구현할 때, 우리는 모델의 순전파(forward propagation)을 계산하면서 입력에 대한 모델의 결과만을 계산했습니다. 그리고, 자동으로 생성된 backward 함수를 호출함으로 autograd 을 이용해서 gradient를 계산합니다. 역전파(back-propagation)을 이용하는 경우 자동으로 그래디언트(gradient)를 계산하는 함수를 이용함으로 딥러닝 학습 알고리즘 구현이 굉장히 간단해졌습니다. 이 절에서는 순전파(forward propagation)와 역전파(back propagation)를 수학적이고 연산적인 그래프를 사용해서 설명하겠습니다. 더 정확하게는 한개의 은닉층(hidden layer)를 갖는 다층 퍼셉트론(multilayer perceptron)에 \(\ell_2\) 놈 정규화(norm regularization)를 적용한 간단한 모델을 이용해서 순전파(forward propagation)와 역전파(back propagation)를 설명합니다. 이 절은 딥러닝을 수행할 때 어떤 일이 일어나고 있는지에 대해서 더 잘 이해할 수 있도록 해줄 것입니다.

3.14.1. 순전파(forward propagation)¶ 순전파(forward propagation)은 뉴럴 네트워크 모델의 입력층부터 출력층까지 순서대로 변수들을 계산하고 저장하는 것을 의미합니다. 지금부터 한개의 은닉층(hidden layer)을 갖는 딥 네트워크를 예로 들어 단계별로 어떻게 계산되는지 설명하겠습니다. 다소 지루할 수 있지만, backward 를 호출했을 때, 어떤 일이 일어나는지 논의할 때 도움이 될 것입니다. 간단하게 하기 위해서, 입력은 \(d\) 차원의 실수 공간 \(\mathbf{x}\in \mathbb{R}^d\) 으로 부터 선택되고, 편향(bias) 항목은 생략하겠습니다. 중간 변수는 다음과 같이 정의됩니다. \[\mathbf{z}= \mathbf{W}^{(1)} \mathbf{x}\] \(\mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}\) 은 은닉층(hidden layer)의 가중치 파라미터입니다. 중간 변수 \(\mathbf{z}\in \mathbb{R}^h\) 를 활성화 함수(activation functino) \(\phi\) 에 입력해서 벡터 길이가 \(h\) 인 은닉층(hidden layer) 변수를 얻습니다. \[\mathbf{h}= \phi (\mathbf{z}).\] 은닉 변수 \(\mathbf{h}\) 도 중간 변수입니다. 출력층의 가중치 \(\mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}\) 만을 사용한다고 가정하면, 벡터 길이가 \(q\) 인 출력층의 변수를 다음과 같이 계산할 수 있습니다. \[\mathbf{o}= \mathbf{W}^{(2)} \mathbf{h}.\] 손실 함수(loss function)를 \(l\) 이라고 하고, 샘플 레이블을 \(y\) 라고 가정하면, 하나의 데이터 샘플에 대한 손실(loss) 값을 다음과 같이 계산할 수 있습니다. \[L = l(\mathbf{o}, y).\] \(\ell_2\) 놈 정규화(norm regularization)의 정의에 따라서, 하이퍼파라미터(hyper-parameter) \(\lambda\) 가 주어졌을 때, 정규화 (regularization) 항목은 다음과 같습니다. \[s = \frac{\lambda}{2} \left(\|\mathbf{W}^{(1)}\|_F^2 + \|\mathbf{W}^{(2)}\|_F^2\right),\] 여기서 행렬의 Frobenius norm은 행렬을 벡터로 바꾼 후 계산하는 \(L_2\) 놈(norm)과 같습니다. 마지막으로, 한개의 데이터 샘플에 대한 모델의 정규화된 손실(regularized loss) 값을 계산합니다. \[J = L + s.\] \(J\) 를 주어진 데이터 샘플에 대한 목표 함수(objective function)라고 하며, 앞으로 이를 ’목표 함수(objective function)’라고 하겠습니다.

3.14.2. 순전파(forward propagation)의 연산 그래프¶ 연산 그래프를 도식화하면 연산에 포함된 연산자와 변수들 사이의 관계를 시각화 하는데 도움이 됩니다. 아래 그림은 위에서 정의한 간단한 네트워크의 그래프입니다. 왼쪽 아래는 입력이고, 오른쪽 위는 출력입니다. 데이터의 흐름을 표시하는 화살표의 방향이 오른쪽과 위로 향해 있습니다.

3.14.3. 역전파(back propagation)¶ 역전파(back propagation)는 뉴럴 네트워크의 파라미터들에 대한 그래디언트(gradient)를 계산하는 방법을 의미합니다. 일반적으로는 역전파(back propagation)은 뉴럴 네트워크의 각 층과 관련된 목적 함수(objective function)의 중간 변수들과 파라미터들의 그래디언트(gradient)를 출력층에서 입력층 순으로 계산하고 저장합니다. 이는 미적분의 ’체인룰(chain rule)’을 따르기 때문입니다. 임의의 모양을 갖는 입력과 출력 텐서(tensor) \(\mathsf{X}, \mathsf{Y}, \mathsf{Z}\) 들을 이용해서 함수 \(\mathsf{Y}=f(\mathsf{X})\) 와 \(\mathsf{Z}=g(\mathsf{Y}) = g \circ f(\mathsf{X})\) 를 정의했다고 가정하고, 체인룰(chain rule)을 사용하면, \(\mathsf{X}\) 에 대한 \(\mathsf{Z}\) 의 미분은 다음과 같이 정의됩니다. \[\frac{\partial \mathsf{Z}}{\partial \mathsf{X}} = \text{prod}\left(\frac{\partial \mathsf{Z}}{\partial \mathsf{Y}}, \frac{\partial \mathsf{Y}}{\partial \mathsf{X}}\right).\] 여기서 \(\text{prod}\) 연산은 전치(transposotion)나 입력 위치 변경과 같이 필요한 연산을 수항한 후 곱을 수행하는 것을 의미합니다. 벡터의 경우에는 이것은 직관적입니다. 단순히 행렬-행렬 곱셈이고, 고차원의 텐서의 경우에는 새로 대응하는 원소들 간에 연산을 수행합니다. \(\text{prod}\) 연산자는 이 모든 복잡한 개념을 감춰주는 역할을 합니다. 하나의 은닉층(hidden layer)를 갖는 간단한 네트워크의 파라매터는 \(\mathbf{W}^{(1)}\) 와 \(\mathbf{W}^{(2)}\) 이고, 역전파(back propagation)는 미분값 \(\partial J/\partial \mathbf{W}^{(1)}\) 와 \(\partial J/\partial \mathbf{W}^{(2)}\) 를 계산하는 것입니다. 이를 위해서 우리는 체인룰(chain rule)을 적용해서 각 중간 변수와 파라미터에 대한 그래디언트(gradient)를 계산합니다. 연산 그래프의 결과로부터 시작해서 파라미터들에 대한 그래디언트(gradient)를 계산해야하기 때문에, 순전파(forward propagation)와는 반대 방향으로 연산을 수행합니다. 첫번째 단계는 손실(loss) 항목 \(L\) 과 정규화(regularization) 항목 \(s\) 에 대해서 목적 함수(objective function) \(J=L+s\) 의 그래디언트(gradient)를 계산하는 것입니다. \[\frac{\partial J}{\partial L} = 1 \text{ and } \frac{\partial J}{\partial s} = 1\] 그 다음, 출력층 \(o\) 의 변수들에 대한 목적 함수(objective function)의 그래디언트(gradient)를 체인룰(chain rule)을 적용해서 구합니다. \[\frac{\partial J}{\partial \mathbf{o}} = \text{prod}\left(\frac{\partial J}{\partial L}, \frac{\partial L}{\partial \mathbf{o}}\right) = \frac{\partial L}{\partial \mathbf{o}} \in \mathbb{R}^q\] 이제 두 파라메터에 대해서 정규화(regularization) 항목의 그래디언트(gradient)를 계산합니다. \[\frac{\partial s}{\partial \mathbf{W}^{(1)}} = \lambda \mathbf{W}^{(1)} \text{ and } \frac{\partial s}{\partial \mathbf{W}^{(2)}} = \lambda \mathbf{W}^{(2)}\] 이제 우리는 출력층와 가장 가까운 모델 파라미터들에 대해서 목적 함수(objective function)의 그래디언트(gradient) \(\partial J/\partial \mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}\) 를 계산할 수 있습니다. 체인룰(chain rule)을 적용하면 다음과 같이 계산됩니다. \[\frac{\partial J}{\partial \mathbf{W}^{(2)}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{W}^{(2)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(2)}}\right) = \frac{\partial J}{\partial \mathbf{o}} \mathbf{h}^\top + \lambda \mathbf{W}^{(2)}\] \(\mathbf{W}^{(1)}\) 에 대한 그래디언트(gradient)를 계산하기 위해서, 출력층으로부터 은닉층까지 역전파(back propagation)를 계속 해야합니다. 은닉층(hidden layer) 변수에 대한 그래디언트(gradient) \(\partial J/\partial \mathbf{h}\in \mathbb{R}^h\) 는 다음과 같습니다. \[\frac{\partial J}{\partial \mathbf{h}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{h}}\right) = {\mathbf{W}^{(2)}}^\top \frac{\partial J}{\partial \mathbf{o}}.\] 활성화 함수(activation function) \(\phi\) 는 각 요소별로 적용되기 때문에, 중간 변수 \(\mathbf{z}\) 에 대한 그래디언트(gradient) \(\partial J/\partial \mathbf{z}\in \mathbb{R}^h\) 를 계산하기 위해서는 요소별 곱하기(element-wise multiplication) 연산자를 사용해야합니다. 우리는 이 연산을 \(\odot\) 로 표현하겠습니다. \[\frac{\partial J}{\partial \mathbf{z}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{h}}, \frac{\partial \mathbf{h}}{\partial \mathbf{z}}\right) = \frac{\partial J}{\partial \mathbf{h}} \odot \phi’\left(\mathbf{z}\right).\] 마지막으로, 입력층과 가장 가까운 모델 파라미터에 대한 그래디언트(gradient) \(\partial J/\partial \mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}\) 를 체인룰(chain rule)을 적용해서 다음과 같이 계산합니다. \[\frac{\partial J}{\partial \mathbf{W}^{(1)}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{z}}, \frac{\partial \mathbf{z}}{\partial \mathbf{W}^{(1)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(1)}}\right) = \frac{\partial J}{\partial \mathbf{z}} \mathbf{x}^\top + \lambda \mathbf{W}^{(1)}.\]

3.14.4. 모델 학습시키기¶ 네트워크를 학습시킬 때, 순전파(forward propagation)과 역전파(backward propagation)은 서로 의존하는 관계입니다. 특히 역전파(forward propagation)는 연관되는 관계를 따라서 그래프를 계산하고, 그 경로의 모든 변수를 계산합니다. 이것들은 연산이 반대 방향인 역전파(back propagation)에서 다시 사용됩니다. 그 결과 중에 하나로 역전파(back propagation)을 완료할 때까지 중간 값들을 모두 가지고 있어야하는 것이 있습니다. 이것이 역전파(back propagation)가 단순 예측을 수행할 때보다 훨씬 더 많은 메모리를 사용하는 이유들 중에 하나입니다. 즉, 체인룰(chain rule)을 적용하기 위해서 모든 중간 변수를 저장하고 있어야, 그래디언트(gradient)인 텐서(tensor)들을 계산할 수 있습니다. 메모리를 더 많이 사용하는 다른 이유는 모델을 학습 시킬 때 미니 배치 형태로 하기 때문에, 더 많은 중간 활성화(activation)들을 저장해야하는 것이 있습니다.

3.14.5. 요약¶ 순전파(forwards propagation)은 뉴럴 네트워크의 그래프를 계산하기 위해서 중간 변수들을 순서대로 계산하고 저장합니다. 즉, 입력층부터 시작해서 출력층까지 처리합니다.

역전파(back propagation)은 중간 변수와 파라미터에 대한 그래디언트(gradient)를 반대 방향으로 계산하고 저장합니다.

딥러닝 모델을 학습시킬 때, 순전파(forward propagation)과 역전파(back propagation)는 상호 의존적입니다.

학습은 상당히 많은 메모리와 저장 공간을 요구합니다.

3.14.6. 문제¶ 입력 \(\mathbf{x}\) 가 행렬이라고 가정하면, 그래디언트(gradient)의 차원이 어떻게 되나요? 이 절에서 설명한 모델의 은닉층(hidden layer)에 편향(bias)을 추가하고, 연산 그래프를 그려보세요

순전파(forward propagation)와 역전파(backward propagation) 공식을 유도해보세요. 이 절에 사용한 모델에 대해서 학습과 예측에 사용되는 메모리 양을 계산해보세요. 2차 미분을 계산을 해야한다고 가정합니다. 그래프 연산에 어떤 일이 생길까요? 좋은 아이디어인가요? 연산 그래프가 사용 중인 GPU에 비해서 너무 크다고 가정합니다. 한개 이상의 GPU로 나눌 수 있나요?

작은 미니배치로 학습을 할 경우 장점과 단점이 무엇인가요?

[35편] 딥러닝의 핵심 개념 – 역전파(backpropagation) 이해하기1

1958년 퍼셉트론이 발표된 후 같은 해 7월 8일자 뉴욕타임즈는 앞으로 조만간 걷고, 말하고 자아를 인식하는 단계에 이르는 컴퓨터 세상이 도래할 것이라는 다소 과격한 기사를 냈습니다.

하지만 1969년, 단순 퍼셉트론은 ​XOR 문제도 풀 수 없다는 사실을 MIT AI 랩 창시자인 Marvin Minsky 교수가 증명하였고, 다층 퍼셉트론(MLP)으로 신경망을 구성하면 XOR 문제를 풀 수 있으나, 이러한 MLP를 학습시키는 방법은 존재하지 않는다고 단정해버렸습니다.

이로 인해 떠들석했던 인공신경망과 관련된 학문과 기술은 더 이상 발전되지 않고 침체기를 겪게 됩니다.

그런데 1974년, 당시 하버드 대학교 박사과정이었던 Paul Werbos는 MLP를 학습시키는 방법을 찾게 되는데, 이 방법을 Minsky 교수에게 설명하지만 냉랭한 분위기속에 무시되버립니다.

Paul Werbos가 Minsky 교수에게 설명한 MLP를 학습시킬 수 있는 획기적인 방법이 바로 오류 역전파 (Backpropagation of errors)라는 개념입니다.

이제 오류 역전파(앞으로 그냥 역전파라고 부르겠습니다)가 무엇인지 살펴보도록 합니다.

심층 신경망을 학습한다는 것은 최종 출력값과 실제값의 오차가 최소가 되도록 심층 신경망을 이루는 각 층에서 입력되는 값에 곱해지는 가중치와 바이어스를 계산하여 결정하는 것을 말합니다.

우리는 [6편] 아달라인을 학습할 때 인공 신경망의 출력값과 실제값의 오차가 최소가 되도록 가중치를 결정하는 방법인 경사하강법에 대해 다룬적이 있습니다.

경사하강법은 오차 곡선을 따라 일정한 크기로 내려가면서 최소값을 찾아가는 방법인데, 일정한 크기는 해당 지점에서 접선의 기울기와 learning rate으로 결정한다고 했지요.

기억이 가물거리면 다시 [6편]을 학습하고 옵니다.

☞ 경사하강법 다시보러 가기

심층 신경망을 학습하는데 유용하게 활용되는 역전파(backpropagtion) 알고리즘도 결국 이 경사하강법을 이용합니다. 그러면 역전파가 무엇인지 그 개념에 대해 대충(?) 살펴보도록 합니다.

먼저 아래와 같은 2-2-2 다층 퍼셉트론이 있다고 생각해봅니다. 여기서는 역전파의 개념만 살펴볼 예정이므로 각 층에 존재하는 바이어스는 생략했습니다. 또한 용어의 통일성을 위해 입력층의 x1, x2는 a1(1), a2(1)로 표기하기로 합니다.

[34편]에서 l층의 i번째 노드와 l+1층의 j번째 노드를 연결하는 가중치 w를 다음과 같이 정의했지요~

이 구조에서 적용되는 활성 함수는 시그모이드 함수 φ입니다.

입력층 -> 은닉층 사이의 값의 흐름은 다음과 같습니다.

[식1]

그리고 은닉층 -> 출력층 사이의 값의 흐름은 다음과 같습니다.

[식2]

이와 같이 입력층의 a1(1), a2(1)을 시작으로 출력층의 a1(3), a2(3) 값이 출력되는 과정을 순전파(feedforward)라 부릅니다.

우리는 아달라인을 다룰 때 오차제곱합을 비용함수로 도입해서, 이 비용함수가 최소가 되도록 가중치를 결정했습니다. 물론 가중치를 결정하는 방법은 경사하강법이었죠.

여기서도 비용함수 J를 오차제곱합으로 정의를 해보면 다음과 같이 될 겁니다.

여기서 y1, y2는 트레이닝 데이터에 대한 각 노드에 대응되는 실제값입니다. 순전파 1회가 마무리되면 J1과 J2의 값이 결정되겠죠.

입력값 a1(1), a2(1)과 실제값 y1, y2는 고정된 값이므로 J1과 J2는 결국 가중치 w1,1(1)~w2,2(2)를 변수로 가지는 함수가 됩니다.

우리의 목표는 J1과 J2의 값이 최소가 되도록 w1,1(1)~w2,2(2)를 결정해야 합니다.

아달라인에서와 마찬가지로 다층 퍼셉트론에서도 비용함수의 최소값을 찾아가는 방법으로 경사하강법을 활용한다고 했습니다. 각 층에서 가중치를 업데이트하기 위해서는 결국 각 층에서의 비용함수(오차로 불러도 됩니다.)의 미분값이 필요하게 되는 것이지요.

이를 매우 효율적으로 해결하기 위한 방법이 바로 역전파 알고리즘입니다.

역전파란 역방향으로 오차를 전파시키면서 각층의 가중치를 업데이트하고 최적의 학습 결과를 찾아가는 방법입니다.

자, 이 경우를 한번 생각해봅니다.

순전파에 있어서 가중치의 값을 매우 미세하게 변화시키면 비용함수 J1, J2도 매우 미세하게 변화될 겁니다. 매우 미세하게 변화시킨다는 것은 미분을 한다는 이야기입니다. 그런데, 매우 미세한 영역으로 국한할 때 가중치의 미세변화와 이에 따른 비용함수의 미세변화가 선형적인 관계가 된다고 알려져 있습니다. 선형적인 관계라는 이야기는 결국 비용함수를 매우 미세하게 변화시키면 이에 따라 가중치도 매우 미세하게 변화되는데 이 역시 선형적인 관계라는 이야기입니다.

이런 원리에 의해, 순전파를 통해 출력층에서 계산된 오차 J1, J2의 각 가중치에 따른 미세변화를 입력층 방향으로 역전파시키면서 가중치를 업데이트하고, 또 다시 입력값을 이용해 순전파시켜 출력층에서 새로운 오차를 계산하고, 이 오차를 또다시 역전파시켜 가중치를 업데이트하는 식으로 반복합니다.

역전파를 이용한 가중치 업데이트 절차는 아래와 같이 요약될 수 있습니다.

주어진 가중치 값을 이용해 출력층의 출력값을 계산함(순전파를 통해 이루어짐 ) 오차를 각 가중치로 미분한 값(실제로는 learning rate을 곱한 값)을 기존 가중치에서 빼줌(경사하강법을 적용하는 것이며, 역전파를 통해 이루어짐) 2번 단계는 모든 가중치에 대해 이루어짐 1~3단계를 주어진 학습회수만큼 또는 주어진 허용오차값에 도달할 때가지 반복함

2번 단계의 가중치 업데이트 식을 수식으로 표현하면 다음과 같습니다.

여기서 Jtotal은 해당 노드가 영향을 미치는 오차의 총합입니다. 예를 들면 출력층의 노드 a1(3)에서는 다른 노드의 오차에 영향을 전혀 미치지 않으므로 해당 노드에서 오차인 J1만 따지면 되지만, 은닉층의 a1(2) 노드는 출력층의 a1(3), a2(3) 노드의 오차에 영향을 미치므로 J1, J2 모두 더한 것이 Jtotal 값이 됩니다.

이번에는 역전파를 통한 가중치 업데이트 메커니즘을 살펴볼 것이므로 편의상 learning rate η는 1로 둡니다.

자, 그러면 실제로 역전파를 이용해 가중치를 업데이트 해보도록 하죠. 먼저 가중치 w1,1(2)에 대해서 계산을 해봅니다.

w1,1(2)에 대한 업데이트 식은 아래와 같습니다.

미분의 연쇄법칙에 의해 오차의 가중치에 대한 미분값은 아래의 식으로 표현될 수 있습니다.

이제 위 식의 오른쪽 항에 있는 3개의 미분값을 하나하나 구해보도록 하지요~ 참고로 이 포스팅의 첫부분에서 서술한 순전파를 통한 값의 흐름을 요약한 수식을 참고하기 바랍니다.

역전파의 출발노드인 a1(3)에서 Jtotal의 값은 J1입니다. 따라서 위 식 오른쪽 항은 아래와 같이 계산됩니다.

따라서

역전파에서 가중치 업데이트를 위해 사용되는 오차의 가중치에 대한 미분값이 결국 역전파의 출발 노드의 활성 함수 값과 도착 노드의 활성 함수 값, 그리고 실제값만으로 표현되는 것을 알 수 있습니다.

​위 식의 오른쪽 항에서 처음 두 식의 값을 아래와 같이 δ1(3)으로 둡니다.

[식3]

역전파에 의해 업데이트 되는 w1,1(2)는 다음과 같습니다.

마찬가지 방법으로 w1,2(2)에 대한 업데이트 수식도 다음과 같습니다.

δ2(3)을 아래와 같은 수식으로 정의하고,

[식4]

w2,1(2), w2,2(2)에 대해서도 계산을 해보면

여기까지 서술해보니 뭔가 규칙성이 보입니다. 출력층에서 은닉층으로 역전파를 통해 전달되는 값이 결국 δ1(3) 과 δ2(3) 뿐이더라도 관련된 가중치를 업데이트하는데 충분하다는 것을 알 수 있습니다.

이제, 은닉층에서 입력층으로 향하는 역전파를 통해 w1,1(1)의 값을 업데이트 해보겠습니다.

w1,1(1)을 업데이트 하기 위해서 a1(2)에서 Jtotal을 w1,1(1)로 편미분한 값을 계산해야겠지요? 앞에서 적용한 것과 마찬가지로 미분의 연쇄법칙을 활용하면 다음과 같은 식이 됩니다.

위에서 언급했듯이 a1(2)에서 Jtotal은 J1과 J2를 더한 값이 됩니다. 따라서 위 식 오른쪽 항의 첫 번째 편미분값은 아래와 같이 계산됩니다.

그런데 출력층 -> 은닉층으로 역전파를 통한 가중치 계산시에 등장한 [식3]과 [식4]와 [식1], [식2]를 참고하여 계산해보면 다음과 같은 결과가 나옵니다.

가 됩니다.

나머지 편미분 값은 이미 해본 계산이므로 쉽게 계산됩니다. w1,1(1)을 업데이트 하기 위한 편미분 값은 다음과 같습니다.

위 식의 오른쪽 항에서 처음 두 식을 δ1(2)로 둡니다.

최종적으로 w1,1(1)의 업데이트 식은 아래와 같게 됩니다.

동일하게 w1,2(1)의 업데이트 식은 아래와 같습니다.

마찬가지로, δ2(2)를 다음과 같이 두고,

w2,1(1), w2,2(2)에 대한 업데이트 식을 나타내면 다음과 같습니다.

따라서 모든 가중치에 대한 업데이트 식은 아래와 같이 표현할 수 있습니다.

여태까지 다룬 역전파 알고리즘 개념을 그림으로 도식화하여 나타내보면 다음과 같습니다.

이와 같이 순전파 -> 역전파 -> 가중치 업데이트 -> 순전파 …. 로 계속 반복해나가면 오차값이 0에 가까와지게 됩니다.

이상으로 역전파 알고리즘 및 가중치 업데이트 원리를 살펴보았습니다. 역전파 알고리즘을 이해하기가 쬐금은 어렵지만, 이 부분이 딥러닝의 핵심적인 역할을 하는 부분이며, 완전한 이해가 힘들더라도 그 개념만이라도 알고 있으면 많은 도움이 됩니다.

텐서 플로우 블로그 (Tensor ≈ Blog)

OpenAI의 안드레이 카패시(Andrej Karpathy)가 얼마전 ‘Yes you should understood backprop‘란 글을 미디엄 사이트에 올렸습니다. 안드레이는 OpenAI에 오기 전에 스탠포드 대학에서 PhD 학생으로 근무했고 CS231n 강의를 진행했습니다. 이 강의는 영상과 강의 노트 모두 인터넷에 공개되어 있어 인공지능에 관심있는 연구자나 학생들에게 인기가 많습니다. 그런데 학생들이 가끔 CS231n 의 숙제에 불평을 하는 경우가 있는 모양입니다. 텐서플로우 같은 라이브러리가 역전파 알고리즘을 모두 자동으로 처리해 주는 데 굳이 numpy 로 정방향(forward pass), 역방향(backward pass, backpropagation) 코드를 직접 구현하는 숙제를 할 필요가 있느냐고 말이죠.

역전파 코드를 직접 만들어 봐야할 이유가 지적 호기심이나 더 나은 역전파 알고리즘을 만들기 위해서가 아니라 역전파를 알아야 뉴럴 네트워크를 만들 때 오류를 범하지 않고 디버깅을 하는데 도움을 주기 때문입니다. 안드레이는 역전파가 불완전한 추상화(leaky abstraction)라고 말하고 있습니다. 불완전한 추상화 또는 누수 추상화는 조엘 스폴스키의 블로그를 통해서 알려졌었습니다. 한마디로 구멍이 많다는거죠!

시그모이드 함수의 예에서 역전파가 전혀 이루어지지 않고 그래디언트가 사라져 버리는 경우를 설명하고 있습니다. 흔히 이를 배니싱 그래디언트(vanishing gradient)라고 부릅니다. 즉 그래디언트가 사라져 버려서 역전파가 안되고 파라미터가 학습이 되지 않는다는 뜻입니다. 시그모이드 함수의 식과 도함수는 아래와 같습니다(편의상 바이어스는 입력 벡터에 포함되어 있다고 생각합니다):

, ,

따라서 안드레이가 제시한 코드와 같이 정방향, 역방향 계산은 간단한 numpy 코드로 만들 수 있습니다.

z = 1/(1 + np.exp(-np.dot(W, x))) # 정방향, z는 W의 행 개수와 동일한 크기의 벡터 dx = np.dot(W.T, z*(1-z)) # 역방향: x를 위한 로컬 그래디언트 dW = np.outer(z*(1-z), x) # 역방향: W를 위한 로컬 그래디언트, outer로 원래 W 행렬 사이즈 복원

가중치 파라미터가 큰 값으로 초기화되면 np.dot(W, x) 값이 커지고 큰 양수나 큰 음수가 될 것입니다. 이는 시그모드이 함수의 출력(z)를 1 또는 0 에 가깝게 만듭니다. 그런데 이 때, 즉 z = 0 or 1 , 시그모이드 도함수는 z*(1-z) 이므로 두 경우 모두 쉽게 0 의 값을 가질 수 있게 됩니다. 이 시그모이드 활성화 함수의 그래디언트가 0 이 되면 체인룰에 의해 이후 네트워크로 전달되는 모든 그래디언트도 자동으로 0 이 됩니다. 즉 W 파라미터가 학습되지 않을 것입니다. 이런 상황을 포화되었다고(saturated) 말하고 있습니다. 그래프로 보시면 조금 더 이해가 빠릅니다.

시그모이드 함수는 S 자 곡선을 그리며 0 과 1 로 수렴합니다. 시그모이드의 도함수는 t = 0 일 때 z = 0.5 가 되며 이 때 도함수 가 가장 큰 값 0.25가 됩니다. z = 0 or 1 일 땐 도 함수 값은 모두 0 으로 수렴합니다. 반대로 생각하면 시그모이드 함수를 쓰는 한 출력 z 가 아무리 큰 값을 가지더라도 역전파될 때 그래디언트는 최소한 1/4 만큼 줄어든다는 뜻입니다. 따라서 기본적인 SGD 방법에서 뒤쪽 레이어가 앞쪽 레이어보다 학습이 느리게 될 수 밖에 없습니다.

하이퍼볼릭 탄젠트(tanh) 활성화 함수의 경우 -1 ~ 1 사이의 결과 값을 가집니다. 이 경우에도 마찬가지로 쌍곡선함수의 도함수가 이므로 모두 그래디언트가 0 이 됩니다. 그래서 가중치의 초기화, 규제(regularization) 또 입력 데이터의 정규화가 중요할 수 밖에 없습니다. CS231n 의 강좌를 참고로 알려 주었습니다.

렐루(ReLU) 활성화 함수는 음수 값은 버리고 양수 값은 그대로 바이패스하는 간단한 함수 입니다. 코드로 나타내면 아래와 같습니다.

z = np.maximum(0, np.dot(W, x)) # 정방향 dW = np.outer(z > 0, x) # 역방향: W의 로컬 그래디언트 # 렐루의 출력(z)가 음수일 땐 x에 0이 곱해지므로 가중치 파라미터에 업데이트 되는 값이 없습니다.

이 함수의 도함수는 z > 0 일 땐 1 이 되고 z = 0 일 땐 0 이 됩니다.

렐루 함수의 그래디언트가 0 이라는 것은 앞서 말한 대로 가중치 파라미터 W 가 업데이트 되지 않는다는 뜻입니다. 만약 배치 그래디언트 디센트에서 전체 데이터를 주입한 후 일부 뉴런의 그래디언트가 이와 같이 0 이 된다면 이 뉴런은 훈련 세트가 변경되지 않는 한 계속 죽은 상태 즉 학습이 되지 않는 상태로 남게 될 것입니다. 학습 속도(learning rate)가 너무 커서 파라미터를 급격히 변경시킨 것이 오히려 상황을 악화시켜 경우 이런 상황에 빠질 수도 있습니다. 어떤 경우에라도 네트워크의 뉴런 일부가 학습되지 않는 멍텅구리가 되는 건 누구라도 바라는 상황은 아닙니다. 관련된 CS231n 강의 영상도 함께 제시하였습니다.

RNN 의 경우 히든 상태를 저장하고 있는 hs[t] 를 이전 히든 상태 hs[t-1] 로 부터 만들어 냅니다. 안드레이가 제시한 코드에서는 입력 값 x 를 고려하지 않았습니다.

for t in xrange(T): ss[t] = np.dot(Whh, hs[t-1]) hs[t] = np.maximum(0, ss[t]) … for t in reversed(xrange(T)): dss[t] = (hs[t] > 0) * dhs[t] dhs[t-1] = np.dot(Whh.T, dss[t])

이 예에서는 렐루 활성화 함수를 사용하여 hs[t] 를 만들었고 뉴런의 출력 값은 ss[t] 입니다. 역전파가 이루어지게 되면 각 시간 스텝에서 hs[t] 의 값이 0 보다 컸는지를 확인하고 네트워크의 앞에서 전달 받은 그래디언트 dhs[t] 를 다음 시간 스텝으로 전달할지 결정합니다. 그래디언트가 전달될 수 있는 상황이라고 가정하면 이전 스텝의 히든 상태는 시그모이드의 예와 마찬가지로 가중치 매트릭스와의 곱이 됩니다. RNN 에서 히든 상태에 대한 가중치 매트릭스는 하나이므로 시간의 역순으로 그래디언트를 계속 전달할 때에도 곱해지는 가중치 매트릭스트는 변하지 않고 계속 앞의 그래디언트에 반복적으로 곱해집니다. 그러므로 가중치가 1 보다 큰 값을 가지고 있을 때 그래디언트가 폭주(exploding)할 수 있습니다. RNN 에서 그래디언트 클리핑(clipping)을 신경써야 하는 이유입니다. 역시 CS231n 강의를 참고로 소개하고 있습니다.

그런데 안드레이가 이 글을 쓴 이유는 한 코드를 우연히 발견해서입니다. 딥 큐 러닝(Deep Q Learning)을 구현한 한 텐서플로우 코드에서 아래와 같이 델타(delta)를 클리핑하고 있습니다.

self.clipped_delta = tf.clip_by_value(self.delta, -1, 1, name=’clipped_delta’) … self.loss = tf.reduce_mean(tf.square(self.clipped_delta), name=’loss’)

-1 ~ 1 사이로 델타 값을 클리핑할 경우 이 범위 밖에서는 렐루 함수의 경우와 마찬가지로 로컬 그래디언트가 0 이 되기 때문에 손실 함수의 학습시킬 수 없게 됩니다. 안드레이는 후버(Huber) 손실 함수를 대신 사용하도록 권고하였습니다. 정방향에서는 델타의 범위가 -1 ~ 1 을 벗어날 경우 델타의 절대값에서 0.5 를 빼게 됩니다. 역방향 계산에서는 L1 손실의 도함수처럼 x 의 부호에 따라 1 또는 -1 이 됩니다.

self .loss = tf.reduce_mean( clipped_error ( self . delta ), name = ‘ loss ‘ ) … def clipped_error(x): return tf.select(tf.abs(x) < 1.0, 0.5 * tf.square(x), tf.abs(x) - 0.5) 그러니 뉴럴 네트워크를 잘 만들려면 직접 정방향, 역방향 코드를 만들어 보라고 권합니다. 또 CS231n의 역전파 강의도 참고하라고 홍보하네요. 사실 이 코드는 데브시스터즈의 깃허브에 있는 것으로 강화학습과 텐서플로우에 정통하신 김태훈님이 만들었습니다. 그리고 안드레이의 이슈는 즉각 수정되었습니다. 역시 고수는 고수를 알아보는 것이죠! 🙂

COMMON] 신경망 역전파 알고리즘 사용하기

728×90

반응형

728×170

TestProject.zip 다운로드

▶ NeuralNetwork.cs

using System; namespace TestProject { ///

/// 신경망 ///

public class NeuralNetwork { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field ///

/// 난수 발생기 ///

private static Random _random; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field ///

/// 입력 노드 카운트 ///

private int inputNodeCount; ///

/// 은닉 노드 카운트 ///

private int hiddenNodeCount; ///

/// 출력 노드 카운트 ///

private int outputNodeCount; ///

/// 입력 노드 값 배열 ///

private double[] inputNodeValueArray; ///

/// 입력-은닉 가중치 배열 ///

private double[][] inputHiddenWeightArray; ///

/// 은닉 노드 바이어스 배열 ///

private double[] hiddenNodeBiasArray; ///

/// 은닉 노드 값 배열 ///

private double[] hiddenNodeValueArray; ///

/// 은닉-출력 가중치 배열 ///

private double[][] hiddenOutputWeightArray; ///

/// 출력 노드 바이어스 배열 ///

private double[] outputNodeBiasArray; ///

/// 출력 노드 값 배열 ///

private double[] outputNodeValueArray; ///

/// 은닉 노드 그라디언트 배열 ///

private double[] hiddenNodeGradientArray; ///

/// 출력 노드 그라디언트 배열 ///

private double[] outputNodeGradientArray; ///

/// 입력-은닉 이전 가중치 델타 배열 ///

private double[][] inputHiddenPreviousWeightDeltaArray; ///

/// 은닉 노드 이전 바이어스 델타 배열 ///

private double[] hiddenNodePreviousBiasDeltaArray; ///

/// 은닉-출력 이전 가중치 델타 배열 ///

private double[][] hiddenOutputPreviousWeightDeltaArray; ///

/// 출력 노드 이전 바이어스 델타 배열 ///

private double[] outputNodePreviousBiasDeltaArray; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 – NeuralNetwork(inputNodeCount, hiddenNodeCount, outputNodeCount) ///

/// 생성자 ///

/// 입력 노드 카운트 /// 은닉 노드 카운트 /// 출력 노드 카운트 public NeuralNetwork(int inputNodeCount, int hiddenNodeCount, int outputNodeCount) { _random = new Random(0); this.inputNodeCount = inputNodeCount; this.hiddenNodeCount = hiddenNodeCount; this.outputNodeCount = outputNodeCount; this.inputNodeValueArray = new double[inputNodeCount]; this.inputHiddenWeightArray = GetMatrixArray(inputNodeCount, hiddenNodeCount); this.hiddenNodeBiasArray = new double[hiddenNodeCount]; this.hiddenNodeValueArray = new double[hiddenNodeCount]; this.hiddenOutputWeightArray = GetMatrixArray(hiddenNodeCount, outputNodeCount); this.outputNodeBiasArray = new double[outputNodeCount]; this.outputNodeValueArray = new double[outputNodeCount]; this.InitializeWeightArray(); this.hiddenNodeGradientArray = new double[hiddenNodeCount]; this.outputNodeGradientArray = new double[outputNodeCount]; this.inputHiddenPreviousWeightDeltaArray = GetMatrixArray(inputNodeCount, hiddenNodeCount); this.hiddenNodePreviousBiasDeltaArray = new double[hiddenNodeCount]; this.hiddenOutputPreviousWeightDeltaArray = GetMatrixArray(hiddenNodeCount, outputNodeCount); this.outputNodePreviousBiasDeltaArray = new double[outputNodeCount]; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 가중치 배열 구하기 – GetWeightArray() ///

/// 가중치 배열 구하기 ///

/// 가중치 배열 public double[] GetWeightArray() { int totalWeightCount = (this.inputNodeCount * this.hiddenNodeCount) + (this.hiddenNodeCount * this.outputNodeCount) + this.hiddenNodeCount + this.outputNodeCount; double[] targetWeightArray = new double[totalWeightCount]; int k = 0; for(int i = 0; i < this.inputNodeCount; i++) { for(int j = 0; j < this.hiddenNodeCount; j++) { targetWeightArray[k++] = this.inputHiddenWeightArray[i][j]; } } for(int i = 0; i < this.hiddenNodeCount; i++) { targetWeightArray[k++] = this.hiddenNodeBiasArray[i]; } for(int i = 0; i < this.hiddenNodeCount; i++) { for(int j = 0; j < this.outputNodeCount; j++) { targetWeightArray[k++] = this.hiddenOutputWeightArray[i][j]; } } for(int i = 0; i < this.outputNodeCount; i++) { targetWeightArray[k++] = this.outputNodeBiasArray[i]; } return targetWeightArray; } #endregion #region 가중치 배열 설정하기 - SetWeightArray(sourceWeightArray) ///

/// 가중치 배열 설정하기 ///

/// 소스 가중치 배열 public void SetWeightArray(double[] sourceWeightArray) { int totalWeightCount = (this.inputNodeCount * this.hiddenNodeCount) + (this.hiddenNodeCount * this.outputNodeCount) + this.hiddenNodeCount + this.outputNodeCount; if(sourceWeightArray.Length != totalWeightCount) { throw new Exception(“잘못된 소스 가중치 배열 길이 입니다.”); } int k = 0; for(int i = 0; i < this.inputNodeCount; i++) { for(int j = 0; j < this.hiddenNodeCount; j++) { this.inputHiddenWeightArray[i][j] = sourceWeightArray[k++]; } } for(int i = 0; i < this.hiddenNodeCount; i++) { this.hiddenNodeBiasArray[i] = sourceWeightArray[k++]; } for(int i = 0; i < this.hiddenNodeCount; i++) { for(int j = 0; j < this.outputNodeCount; j++) { this.hiddenOutputWeightArray[i][j] = sourceWeightArray[k++]; } } for(int i = 0; i < this.outputNodeCount; i++) { this.outputNodeBiasArray[i] = sourceWeightArray[k++]; } } #endregion #region 학습하기 - Train(trainingValueArray, maximumEpochCount, learningRate, momentum) ///

/// 학습하기 ///

/// 학습 값 배열 /// 최대 시대 카운트 /// 학습률 /// 모멘텀 public void Train(double[][] trainingValueArray, int maximumEpochCount, double learningRate, double momentum) { int epochCount = 0; double[] xValueArray = new double[this.inputNodeCount ]; double[] targetValueArray = new double[this.outputNodeCount]; int traingingValueCount = trainingValueArray.Length; int[] sequenceArray = new int[traingingValueCount]; int sequenceCount = sequenceArray.Length; for(int i = 0; i < sequenceCount; ++i) { sequenceArray[i] = i; } while(epochCount < maximumEpochCount) { double mse = GetMeanSquaredError(trainingValueArray); if(mse < 0.04d) { break; } Shuffle(sequenceArray); for(int i = 0; i < traingingValueCount; i++) { int sequenceIndex = sequenceArray[i]; Array.Copy(trainingValueArray[sequenceIndex], xValueArray, this.inputNodeCount); Array.Copy(trainingValueArray[sequenceIndex], this.inputNodeCount, targetValueArray, 0, this.outputNodeCount); ComputeOutputNodeValueArray(xValueArray); UpdateWeightArray(targetValueArray, learningRate, momentum); } epochCount++; } } #endregion #region 출력 노드 배열 계산하기 - ComputeOutputNodeValueArray(sourceInputNodeValueArray) ///

/// 출력 노드 배열 계산하기 ///

/// 소스 입력 노드 값 배열 /// 출력 노드 값 배열 public double[] ComputeOutputNodeValueArray(double[] sourceInputNodeValueArray) { if(sourceInputNodeValueArray.Length != this.inputNodeCount) { throw new Exception(“잘못된 소스 입력 노드 값 배열 길이 입니다.”); } double[] hiddenNodeSummaryArray = new double[this.hiddenNodeCount]; double[] outputNodeSummaryArray = new double[this.outputNodeCount]; int sourceInputNodeValueCount = sourceInputNodeValueArray.Length; for(int i = 0; i < sourceInputNodeValueCount; i++) { this.inputNodeValueArray[i] = sourceInputNodeValueArray[i]; } for(int i = 0; i < this.hiddenNodeCount; i++) { for(int j = 0; j < this.inputNodeCount; j++) { hiddenNodeSummaryArray[i] += this.inputNodeValueArray[j] * this.inputHiddenWeightArray[j][i]; } } for(int i = 0; i < this.hiddenNodeCount; i++) { hiddenNodeSummaryArray[i] += this.hiddenNodeBiasArray[i]; } for(int i = 0; i < this.hiddenNodeCount; i++) { this.hiddenNodeValueArray[i] = GetHyperTangentValue(hiddenNodeSummaryArray[i]); } for(int i = 0; i < this.outputNodeCount; i++) { for(int j = 0; j < this.hiddenNodeCount; j++) { outputNodeSummaryArray[i] += this.hiddenNodeValueArray[j] * this.hiddenOutputWeightArray[j][i]; } } for(int i = 0; i < this.outputNodeCount; i++) { outputNodeSummaryArray[i] += this.outputNodeBiasArray[i]; } double[] softMaximumArray = GetSoftMaximumArray(outputNodeSummaryArray); Array.Copy(softMaximumArray, this.outputNodeValueArray, softMaximumArray.Length); double[] targetOutputNodeValueArray = new double[this.outputNodeCount]; Array.Copy(this.outputNodeValueArray, targetOutputNodeValueArray, targetOutputNodeValueArray.Length); return targetOutputNodeValueArray; } #endregion #region 정확도 구하기 - GetAccuracy(trainingValueArray) ///

/// 정확도 구하기 ///

/// 훈련 값 배열 /// 정확도 public double GetAccuracy(double[][] trainingValueArray) { int correctCount = 0; int wrongCount = 0; double[] xValueArray = new double[this.inputNodeCount ]; double[] targetValueArray = new double[this.outputNodeCount]; double[] yValueArray; int trainingValueCount = trainingValueArray.Length; for(int i = 0; i < trainingValueCount; i++) { Array.Copy(trainingValueArray[i], xValueArray, this.inputNodeCount); Array.Copy(trainingValueArray[i], this.inputNodeCount, targetValueArray, 0, this.outputNodeCount); yValueArray = ComputeOutputNodeValueArray(xValueArray); int maximumIndex = GetMaximumIndex(yValueArray); if(targetValueArray[maximumIndex] == 1d) { correctCount++; } else { wrongCount++; } } return (correctCount * 1d) / (correctCount + wrongCount); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 매트릭스 배열 구하기 - GetMatrixArray(rowCount, columnCount) ///

/// 매트릭스 배열 구하기 ///

/// 행 카운트 /// 컬럼 카운트 /// 매트릭스 배열 private static double[][] GetMatrixArray(int rowCount, int columnCount) { double[][] matrixArray = new double[rowCount][]; int count = matrixArray.Length; for(int i = 0; i < count; i++) { matrixArray[i] = new double[columnCount]; } return matrixArray; } #endregion #region 가중치 배열 초기화 하기 - InitializeWeightArray() ///

/// 가중치 배열 초기화 하기 ///

private void InitializeWeightArray() { int totalWeightCount = (this.inputNodeCount * this.hiddenNodeCount) + (this.hiddenNodeCount * this.outputNodeCount) + this.hiddenNodeCount + this.outputNodeCount; double[] initialWeightArray = new double[totalWeightCount]; double low = -0.01d; double high = 0.01d; int initialWeightCount = initialWeightArray.Length; for(int i = 0; i < initialWeightCount; i++) { initialWeightArray[i] = (high - low) * _random.NextDouble() + low; } SetWeightArray(initialWeightArray); } #endregion #region 하이퍼 탄젠트 값 구하기 - GetHyperTangentValue(x) ///

/// 하이퍼 탄젠트 값 구하기 ///

/// X /// 하이퍼 탄젠트 값 private static double GetHyperTangentValue(double x) { if(x < -20d) { return -1d; } else if(x > 20d) { return 1d; } else { return Math.Tanh(x); } } #endregion #region SOFT-MAX 배열 구하기 – GetSoftMaximumArray(sourceOutputNodeSummaryArray) ///

/// SOFT-MAX 배열 구하기 ///

/// 소스 출력 노드 합산 배열 /// SOFT-MAX 배열 private static double[] GetSoftMaximumArray(double[] sourceOutputNodeSummaryArray) { int sourceOutputNodeSummaryCount = sourceOutputNodeSummaryArray.Length; double maximum = sourceOutputNodeSummaryArray[0]; for(int i = 0; i < sourceOutputNodeSummaryCount; i++) { if(sourceOutputNodeSummaryArray[i] > maximum) { maximum = sourceOutputNodeSummaryArray[i]; } } double scale = 0d; for(int i = 0; i < sourceOutputNodeSummaryCount; ++i) { scale += Math.Exp(sourceOutputNodeSummaryArray[i] - maximum); } double[] targetArray = new double[sourceOutputNodeSummaryCount]; for(int i = 0; i < sourceOutputNodeSummaryCount; ++i) { targetArray[i] = Math.Exp(sourceOutputNodeSummaryArray[i] - maximum) / scale; } return targetArray; } #endregion #region 가중치 배열 갱신하기 - UpdateWeightArray(targetValueArray, learningRate, momentum) ///

/// 가중치 배열 갱신하기 ///

/// 타겟 값 배열 /// 학습률 /// 모멘텀 private void UpdateWeightArray(double[] targetValueArray, double learningRate, double momentum) { if(targetValueArray.Length != this.outputNodeCount) { throw new Exception(“타겟 값 배열 길이가 출력 노드 값 배열과 같지 않습니다.”); } for(int i = 0; i < this.outputNodeCount; i++) { double derivative = (1 - this.outputNodeValueArray[i]) * this.outputNodeValueArray[i]; this.outputNodeGradientArray[i] = derivative * (targetValueArray[i] - this.outputNodeValueArray[i]); } for(int i = 0; i < hiddenNodeCount; i++) { double derivative = (1 - this.hiddenNodeValueArray[i]) * (1 + this.hiddenNodeValueArray[i]); double summary = 0d; for(int j = 0; j < this.outputNodeCount; j++) { double x = this.outputNodeGradientArray[j] * this.hiddenOutputWeightArray[i][j]; summary += x; } this.hiddenNodeGradientArray[i] = derivative * summary; } for(int i = 0; i < this.inputNodeCount; i++) { for(int j = 0; j < this.hiddenNodeCount; j++) { double delta = learningRate * this.hiddenNodeGradientArray[j] * this.inputNodeValueArray[i]; this.inputHiddenWeightArray[i][j] += delta; this.inputHiddenWeightArray[i][j] += momentum * this.inputHiddenPreviousWeightDeltaArray[i][j]; this.inputHiddenPreviousWeightDeltaArray[i][j] = delta; } } for(int i = 0; i < this.hiddenNodeCount; i++) { double delta = learningRate * this.hiddenNodeGradientArray[i]; this.hiddenNodeBiasArray[i] += delta; this.hiddenNodeBiasArray[i] += momentum * this.hiddenNodePreviousBiasDeltaArray[i]; this.hiddenNodePreviousBiasDeltaArray[i] = delta; } for(int i = 0; i < this.hiddenNodeCount; i++) { for(int j = 0; j < this.outputNodeCount; j++) { double delta = learningRate * this.outputNodeGradientArray[j] * this.hiddenNodeValueArray[i]; this.hiddenOutputWeightArray[i][j] += delta; this.hiddenOutputWeightArray[i][j] += momentum * this.hiddenOutputPreviousWeightDeltaArray[i][j]; this.hiddenOutputPreviousWeightDeltaArray[i][j] = delta; } } for(int i = 0; i < this.outputNodeCount; i++) { double delta = learningRate * this.outputNodeGradientArray[i] * 1d; this.outputNodeBiasArray[i] += delta; this.outputNodeBiasArray[i] += momentum * this.outputNodePreviousBiasDeltaArray[i]; this.outputNodePreviousBiasDeltaArray[i] = delta; } } #endregion #region 섞기 - Shuffle(sequenceArray) ///

/// 섞기 ///

/// 시퀀스 배열 private static void Shuffle(int[] sequenceArray) { for(int i = 0; i < sequenceArray.Length; i++) { int randomIndex = _random.Next(i, sequenceArray.Length); int sequence = sequenceArray[randomIndex]; sequenceArray[randomIndex] = sequenceArray[i]; sequenceArray[i] = sequence; } } #endregion #region 평균 제곱 에러 구하기 - GetMeanSquaredError(trainingValueArray) ///

/// 평균 제곱 에러 구하기 ///

/// 훈련 값 배열 /// 평균 제곱 에러 private double GetMeanSquaredError(double[][] trainingValueArray) { double summarySquaredError = 0d; double[] xValueArray = new double[this.inputNodeCount ]; double[] targetValueArray = new double[this.outputNodeCount]; int trainingValueCount = trainingValueArray.Length; for(int i = 0; i < trainingValueCount; i++) { Array.Copy(trainingValueArray[i], xValueArray, this.inputNodeCount); Array.Copy(trainingValueArray[i], this.inputNodeCount, targetValueArray, 0, this.outputNodeCount); double[] yValueArray = ComputeOutputNodeValueArray(xValueArray); for(int j = 0; j < this.outputNodeCount; j++) { double error = targetValueArray[j] - yValueArray[j]; summarySquaredError += error * error; } } return summarySquaredError / trainingValueCount; } #endregion #region 최대 인덱스 구하기 - GetMaximumIndex(vectorArray) ///

/// 최대 인덱스 구하기 ///

/// 벡터 배열 /// 최대 인덱스 private static int GetMaximumIndex(double[] vectorArray) { int maximumIndex = 0; double maximum = vectorArray[0]; int vectorCount = vectorArray.Length; for(int i = 0; i < vectorCount; i++) { if(vectorArray[i] > maximum) { maximum = vectorArray[i]; maximumIndex = i; } } return maximumIndex; } #endregion } }

728×90

▶ Program.cs

using System; namespace TestProject { class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 – Main() ///

/// 프로그램 시작하기 ///

private static void Main() { Console.Title = “신경망 역전파 알고리즘 사용하기”; Console.WriteLine(); Console.WriteLine(“신경망 훈련 데모를 시작한다.”); Console.WriteLine(); Console.WriteLine(“데이터는 유명한 아이리스 꽃 세트이다.”); Console.WriteLine(“꽃받침 길이/너비, 꽃잎 길이/너비에서 종을 예측한다.”); Console.WriteLine(“Iris setosa = 0 0 1, versicolor = 0 1 0, virginica = 1 0 0”); Console.WriteLine(); Console.WriteLine(“실제 데이터와 유사하다 :”); Console.WriteLine(); Console.WriteLine(” 5.1, 3.5, 1.4, 0.2, Iris setosa” ); Console.WriteLine(” 7.0, 3.2, 4.7, 1.4, Iris versicolor”); Console.WriteLine(” 6.3, 3.3, 6.0, 2.5, Iris versinica” ); Console.WriteLine(” ……”); Console.WriteLine(); double[][] sourceValueArray = new double[150][]; #region 소스 값 배열 데이터를 설정한다. sourceValueArray[0] = new double[] { 5.1, 3.5, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[1] = new double[] { 4.9, 3.0, 1.4, 0.2, 0, 0, 1 }; // Iris setosa = 0 0 1 sourceValueArray[2] = new double[] { 4.7, 3.2, 1.3, 0.2, 0, 0, 1 }; // Iris versicolor = 0 1 0 sourceValueArray[3] = new double[] { 4.6, 3.1, 1.5, 0.2, 0, 0, 1 }; // Iris verginica = 1 0 0 sourceValueArray[4] = new double[] { 5.0, 3.6, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[5] = new double[] { 5.4, 3.9, 1.7, 0.4, 0, 0, 1 }; sourceValueArray[6] = new double[] { 4.6, 3.4, 1.4, 0.3, 0, 0, 1 }; sourceValueArray[7] = new double[] { 5.0, 3.4, 1.5, 0.2, 0, 0, 1 }; sourceValueArray[8] = new double[] { 4.4, 2.9, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[9] = new double[] { 4.9, 3.1, 1.5, 0.1, 0, 0, 1 }; sourceValueArray[10] = new double[] { 5.4, 3.7, 1.5, 0.2, 0, 0, 1 }; sourceValueArray[11] = new double[] { 4.8, 3.4, 1.6, 0.2, 0, 0, 1 }; sourceValueArray[12] = new double[] { 4.8, 3.0, 1.4, 0.1, 0, 0, 1 }; sourceValueArray[13] = new double[] { 4.3, 3.0, 1.1, 0.1, 0, 0, 1 }; sourceValueArray[14] = new double[] { 5.8, 4.0, 1.2, 0.2, 0, 0, 1 }; sourceValueArray[15] = new double[] { 5.7, 4.4, 1.5, 0.4, 0, 0, 1 }; sourceValueArray[16] = new double[] { 5.4, 3.9, 1.3, 0.4, 0, 0, 1 }; sourceValueArray[17] = new double[] { 5.1, 3.5, 1.4, 0.3, 0, 0, 1 }; sourceValueArray[18] = new double[] { 5.7, 3.8, 1.7, 0.3, 0, 0, 1 }; sourceValueArray[19] = new double[] { 5.1, 3.8, 1.5, 0.3, 0, 0, 1 }; sourceValueArray[20] = new double[] { 5.4, 3.4, 1.7, 0.2, 0, 0, 1 }; sourceValueArray[21] = new double[] { 5.1, 3.7, 1.5, 0.4, 0, 0, 1 }; sourceValueArray[22] = new double[] { 4.6, 3.6, 1.0, 0.2, 0, 0, 1 }; sourceValueArray[23] = new double[] { 5.1, 3.3, 1.7, 0.5, 0, 0, 1 }; sourceValueArray[24] = new double[] { 4.8, 3.4, 1.9, 0.2, 0, 0, 1 }; sourceValueArray[25] = new double[] { 5.0, 3.0, 1.6, 0.2, 0, 0, 1 }; sourceValueArray[26] = new double[] { 5.0, 3.4, 1.6, 0.4, 0, 0, 1 }; sourceValueArray[27] = new double[] { 5.2, 3.5, 1.5, 0.2, 0, 0, 1 }; sourceValueArray[28] = new double[] { 5.2, 3.4, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[29] = new double[] { 4.7, 3.2, 1.6, 0.2, 0, 0, 1 }; sourceValueArray[30] = new double[] { 4.8, 3.1, 1.6, 0.2, 0, 0, 1 }; sourceValueArray[31] = new double[] { 5.4, 3.4, 1.5, 0.4, 0, 0, 1 }; sourceValueArray[32] = new double[] { 5.2, 4.1, 1.5, 0.1, 0, 0, 1 }; sourceValueArray[33] = new double[] { 5.5, 4.2, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[34] = new double[] { 4.9, 3.1, 1.5, 0.1, 0, 0, 1 }; sourceValueArray[35] = new double[] { 5.0, 3.2, 1.2, 0.2, 0, 0, 1 }; sourceValueArray[36] = new double[] { 5.5, 3.5, 1.3, 0.2, 0, 0, 1 }; sourceValueArray[37] = new double[] { 4.9, 3.1, 1.5, 0.1, 0, 0, 1 }; sourceValueArray[38] = new double[] { 4.4, 3.0, 1.3, 0.2, 0, 0, 1 }; sourceValueArray[39] = new double[] { 5.1, 3.4, 1.5, 0.2, 0, 0, 1 }; sourceValueArray[40] = new double[] { 5.0, 3.5, 1.3, 0.3, 0, 0, 1 }; sourceValueArray[41] = new double[] { 4.5, 2.3, 1.3, 0.3, 0, 0, 1 }; sourceValueArray[42] = new double[] { 4.4, 3.2, 1.3, 0.2, 0, 0, 1 }; sourceValueArray[43] = new double[] { 5.0, 3.5, 1.6, 0.6, 0, 0, 1 }; sourceValueArray[44] = new double[] { 5.1, 3.8, 1.9, 0.4, 0, 0, 1 }; sourceValueArray[45] = new double[] { 4.8, 3.0, 1.4, 0.3, 0, 0, 1 }; sourceValueArray[46] = new double[] { 5.1, 3.8, 1.6, 0.2, 0, 0, 1 }; sourceValueArray[47] = new double[] { 4.6, 3.2, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[48] = new double[] { 5.3, 3.7, 1.5, 0.2, 0, 0, 1 }; sourceValueArray[49] = new double[] { 5.0, 3.3, 1.4, 0.2, 0, 0, 1 }; sourceValueArray[50] = new double[] { 7.0, 3.2, 4.7, 1.4, 0, 1, 0 }; sourceValueArray[51] = new double[] { 6.4, 3.2, 4.5, 1.5, 0, 1, 0 }; sourceValueArray[52] = new double[] { 6.9, 3.1, 4.9, 1.5, 0, 1, 0 }; sourceValueArray[53] = new double[] { 5.5, 2.3, 4.0, 1.3, 0, 1, 0 }; sourceValueArray[54] = new double[] { 6.5, 2.8, 4.6, 1.5, 0, 1, 0 }; sourceValueArray[55] = new double[] { 5.7, 2.8, 4.5, 1.3, 0, 1, 0 }; sourceValueArray[56] = new double[] { 6.3, 3.3, 4.7, 1.6, 0, 1, 0 }; sourceValueArray[57] = new double[] { 4.9, 2.4, 3.3, 1.0, 0, 1, 0 }; sourceValueArray[58] = new double[] { 6.6, 2.9, 4.6, 1.3, 0, 1, 0 }; sourceValueArray[59] = new double[] { 5.2, 2.7, 3.9, 1.4, 0, 1, 0 }; sourceValueArray[60] = new double[] { 5.0, 2.0, 3.5, 1.0, 0, 1, 0 }; sourceValueArray[61] = new double[] { 5.9, 3.0, 4.2, 1.5, 0, 1, 0 }; sourceValueArray[62] = new double[] { 6.0, 2.2, 4.0, 1.0, 0, 1, 0 }; sourceValueArray[63] = new double[] { 6.1, 2.9, 4.7, 1.4, 0, 1, 0 }; sourceValueArray[64] = new double[] { 5.6, 2.9, 3.6, 1.3, 0, 1, 0 }; sourceValueArray[65] = new double[] { 6.7, 3.1, 4.4, 1.4, 0, 1, 0 }; sourceValueArray[66] = new double[] { 5.6, 3.0, 4.5, 1.5, 0, 1, 0 }; sourceValueArray[67] = new double[] { 5.8, 2.7, 4.1, 1.0, 0, 1, 0 }; sourceValueArray[68] = new double[] { 6.2, 2.2, 4.5, 1.5, 0, 1, 0 }; sourceValueArray[69] = new double[] { 5.6, 2.5, 3.9, 1.1, 0, 1, 0 }; sourceValueArray[70] = new double[] { 5.9, 3.2, 4.8, 1.8, 0, 1, 0 }; sourceValueArray[71] = new double[] { 6.1, 2.8, 4.0, 1.3, 0, 1, 0 }; sourceValueArray[72] = new double[] { 6.3, 2.5, 4.9, 1.5, 0, 1, 0 }; sourceValueArray[73] = new double[] { 6.1, 2.8, 4.7, 1.2, 0, 1, 0 }; sourceValueArray[74] = new double[] { 6.4, 2.9, 4.3, 1.3, 0, 1, 0 }; sourceValueArray[75] = new double[] { 6.6, 3.0, 4.4, 1.4, 0, 1, 0 }; sourceValueArray[76] = new double[] { 6.8, 2.8, 4.8, 1.4, 0, 1, 0 }; sourceValueArray[77] = new double[] { 6.7, 3.0, 5.0, 1.7, 0, 1, 0 }; sourceValueArray[78] = new double[] { 6.0, 2.9, 4.5, 1.5, 0, 1, 0 }; sourceValueArray[79] = new double[] { 5.7, 2.6, 3.5, 1.0, 0, 1, 0 }; sourceValueArray[80] = new double[] { 5.5, 2.4, 3.8, 1.1, 0, 1, 0 }; sourceValueArray[81] = new double[] { 5.5, 2.4, 3.7, 1.0, 0, 1, 0 }; sourceValueArray[82] = new double[] { 5.8, 2.7, 3.9, 1.2, 0, 1, 0 }; sourceValueArray[83] = new double[] { 6.0, 2.7, 5.1, 1.6, 0, 1, 0 }; sourceValueArray[84] = new double[] { 5.4, 3.0, 4.5, 1.5, 0, 1, 0 }; sourceValueArray[85] = new double[] { 6.0, 3.4, 4.5, 1.6, 0, 1, 0 }; sourceValueArray[86] = new double[] { 6.7, 3.1, 4.7, 1.5, 0, 1, 0 }; sourceValueArray[87] = new double[] { 6.3, 2.3, 4.4, 1.3, 0, 1, 0 }; sourceValueArray[88] = new double[] { 5.6, 3.0, 4.1, 1.3, 0, 1, 0 }; sourceValueArray[89] = new double[] { 5.5, 2.5, 4.0, 1.3, 0, 1, 0 }; sourceValueArray[90] = new double[] { 5.5, 2.6, 4.4, 1.2, 0, 1, 0 }; sourceValueArray[91] = new double[] { 6.1, 3.0, 4.6, 1.4, 0, 1, 0 }; sourceValueArray[92] = new double[] { 5.8, 2.6, 4.0, 1.2, 0, 1, 0 }; sourceValueArray[93] = new double[] { 5.0, 2.3, 3.3, 1.0, 0, 1, 0 }; sourceValueArray[94] = new double[] { 5.6, 2.7, 4.2, 1.3, 0, 1, 0 }; sourceValueArray[95] = new double[] { 5.7, 3.0, 4.2, 1.2, 0, 1, 0 }; sourceValueArray[96] = new double[] { 5.7, 2.9, 4.2, 1.3, 0, 1, 0 }; sourceValueArray[97] = new double[] { 6.2, 2.9, 4.3, 1.3, 0, 1, 0 }; sourceValueArray[98] = new double[] { 5.1, 2.5, 3.0, 1.1, 0, 1, 0 }; sourceValueArray[99] = new double[] { 5.7, 2.8, 4.1, 1.3, 0, 1, 0 }; sourceValueArray[100] = new double[] { 6.3, 3.3, 6.0, 2.5, 1, 0, 0 }; sourceValueArray[101] = new double[] { 5.8, 2.7, 5.1, 1.9, 1, 0, 0 }; sourceValueArray[102] = new double[] { 7.1, 3.0, 5.9, 2.1, 1, 0, 0 }; sourceValueArray[103] = new double[] { 6.3, 2.9, 5.6, 1.8, 1, 0, 0 }; sourceValueArray[104] = new double[] { 6.5, 3.0, 5.8, 2.2, 1, 0, 0 }; sourceValueArray[105] = new double[] { 7.6, 3.0, 6.6, 2.1, 1, 0, 0 }; sourceValueArray[106] = new double[] { 4.9, 2.5, 4.5, 1.7, 1, 0, 0 }; sourceValueArray[107] = new double[] { 7.3, 2.9, 6.3, 1.8, 1, 0, 0 }; sourceValueArray[108] = new double[] { 6.7, 2.5, 5.8, 1.8, 1, 0, 0 }; sourceValueArray[109] = new double[] { 7.2, 3.6, 6.1, 2.5, 1, 0, 0 }; sourceValueArray[110] = new double[] { 6.5, 3.2, 5.1, 2.0, 1, 0, 0 }; sourceValueArray[111] = new double[] { 6.4, 2.7, 5.3, 1.9, 1, 0, 0 }; sourceValueArray[112] = new double[] { 6.8, 3.0, 5.5, 2.1, 1, 0, 0 }; sourceValueArray[113] = new double[] { 5.7, 2.5, 5.0, 2.0, 1, 0, 0 }; sourceValueArray[114] = new double[] { 5.8, 2.8, 5.1, 2.4, 1, 0, 0 }; sourceValueArray[115] = new double[] { 6.4, 3.2, 5.3, 2.3, 1, 0, 0 }; sourceValueArray[116] = new double[] { 6.5, 3.0, 5.5, 1.8, 1, 0, 0 }; sourceValueArray[117] = new double[] { 7.7, 3.8, 6.7, 2.2, 1, 0, 0 }; sourceValueArray[118] = new double[] { 7.7, 2.6, 6.9, 2.3, 1, 0, 0 }; sourceValueArray[119] = new double[] { 6.0, 2.2, 5.0, 1.5, 1, 0, 0 }; sourceValueArray[120] = new double[] { 6.9, 3.2, 5.7, 2.3, 1, 0, 0 }; sourceValueArray[121] = new double[] { 5.6, 2.8, 4.9, 2.0, 1, 0, 0 }; sourceValueArray[122] = new double[] { 7.7, 2.8, 6.7, 2.0, 1, 0, 0 }; sourceValueArray[123] = new double[] { 6.3, 2.7, 4.9, 1.8, 1, 0, 0 }; sourceValueArray[124] = new double[] { 6.7, 3.3, 5.7, 2.1, 1, 0, 0 }; sourceValueArray[125] = new double[] { 7.2, 3.2, 6.0, 1.8, 1, 0, 0 }; sourceValueArray[126] = new double[] { 6.2, 2.8, 4.8, 1.8, 1, 0, 0 }; sourceValueArray[127] = new double[] { 6.1, 3.0, 4.9, 1.8, 1, 0, 0 }; sourceValueArray[128] = new double[] { 6.4, 2.8, 5.6, 2.1, 1, 0, 0 }; sourceValueArray[129] = new double[] { 7.2, 3.0, 5.8, 1.6, 1, 0, 0 }; sourceValueArray[130] = new double[] { 7.4, 2.8, 6.1, 1.9, 1, 0, 0 }; sourceValueArray[131] = new double[] { 7.9, 3.8, 6.4, 2.0, 1, 0, 0 }; sourceValueArray[132] = new double[] { 6.4, 2.8, 5.6, 2.2, 1, 0, 0 }; sourceValueArray[133] = new double[] { 6.3, 2.8, 5.1, 1.5, 1, 0, 0 }; sourceValueArray[134] = new double[] { 6.1, 2.6, 5.6, 1.4, 1, 0, 0 }; sourceValueArray[135] = new double[] { 7.7, 3.0, 6.1, 2.3, 1, 0, 0 }; sourceValueArray[136] = new double[] { 6.3, 3.4, 5.6, 2.4, 1, 0, 0 }; sourceValueArray[137] = new double[] { 6.4, 3.1, 5.5, 1.8, 1, 0, 0 }; sourceValueArray[138] = new double[] { 6.0, 3.0, 4.8, 1.8, 1, 0, 0 }; sourceValueArray[139] = new double[] { 6.9, 3.1, 5.4, 2.1, 1, 0, 0 }; sourceValueArray[140] = new double[] { 6.7, 3.1, 5.6, 2.4, 1, 0, 0 }; sourceValueArray[141] = new double[] { 6.9, 3.1, 5.1, 2.3, 1, 0, 0 }; sourceValueArray[142] = new double[] { 5.8, 2.7, 5.1, 1.9, 1, 0, 0 }; sourceValueArray[143] = new double[] { 6.8, 3.2, 5.9, 2.3, 1, 0, 0 }; sourceValueArray[144] = new double[] { 6.7, 3.3, 5.7, 2.5, 1, 0, 0 }; sourceValueArray[145] = new double[] { 6.7, 3.0, 5.2, 2.3, 1, 0, 0 }; sourceValueArray[146] = new double[] { 6.3, 2.5, 5.0, 1.9, 1, 0, 0 }; sourceValueArray[147] = new double[] { 6.5, 3.0, 5.2, 2.0, 1, 0, 0 }; sourceValueArray[148] = new double[] { 6.2, 3.4, 5.4, 2.3, 1, 0, 0 }; sourceValueArray[149] = new double[] { 5.9, 3.0, 5.1, 1.8, 1, 0, 0 }; #endregion Console.WriteLine(“150개 항목 데이터 세트의 첫번째 6개 행들 :”); Console.WriteLine(); DisplayMatrixArray(sourceValueArray, 6, 1, true); Console.WriteLine(“80% 훈련 데이터 매트릭스와 20% 테스트 데이터 매트릭스 생성”); double[][] traingingValueArray = null; double[][] testValueArray = null; PrepareData(sourceValueArray, 72, out traingingValueArray, out testValueArray); Console.WriteLine(); Console.WriteLine(“훈련 데이터의 첫번째 3개 행들 :”); Console.WriteLine(); DisplayMatrixArray(traingingValueArray, 3, 1, true); Console.WriteLine(“테스트 데이터의 첫번째 3개 행들 :”); Console.WriteLine(); DisplayMatrixArray(testValueArray, 3, 1, true); Console.WriteLine(“4개 입력 노드, 7개 은닉 노드, 3개 출력 노드를 갖는 신경망 생성”); Console.WriteLine(“입력-은닉층을 위한 하드 코딩된 하이퍼 탄젠트 함수와 은닉-출력층 활성화를 위한 소프트 맥스 함수”); int inputNodeCount = 4; int hiddenModeCount = 7; int outputNodeCount = 3; NeuralNetwork neuralNetwork = new NeuralNetwork(inputNodeCount, hiddenModeCount, outputNodeCount); int maximumEpochCount = 1000; double learningRate = 0.05d; double momentum = 0.01d; Console.WriteLine(“최대 시대 카운트 = ” + maximumEpochCount + “, 학습률 = ” + learningRate + “, 모멘텀 = ” + momentum); Console.WriteLine(“훈련은 하드 코딩된 평균 제곱 오류가 0.040보다 작은 경우 중단하는 조건을 갖는다.”); Console.WriteLine(); Console.WriteLine(“증분 역전파를 사용해 훈련 시작하기”); Console.WriteLine(); neuralNetwork.Train(traingingValueArray, maximumEpochCount, learningRate, momentum); Console.WriteLine(“훈련 완료”); Console.WriteLine(); double[] weightArray = neuralNetwork.GetWeightArray(); Console.WriteLine(“최종 신경망 가중치와 바이어스 값들 : “); DisplayVectorArray(weightArray, 10, 3, true); double trainingAccuracy = neuralNetwork.GetAccuracy(traingingValueArray); double testAccuracy = neuralNetwork.GetAccuracy(testValueArray ); Console.WriteLine(); Console.WriteLine(“훈련 데이터 정확도 = ” + trainingAccuracy.ToString(“F4”)); Console.WriteLine(“테스트 데이터 정확도 = ” + testAccuracy.ToString(“F4”)); Console.WriteLine(); Console.WriteLine(“입력 데이터 : “); double[] inputArray = new double[] { 5.1, 3.5, 1.4, 0.2 }; DisplayVectorArray(inputArray, 4, 3, true); double[] resultArray = neuralNetwork.ComputeOutputNodeValueArray(inputArray); Console.WriteLine(); Console.WriteLine(“출력 데이터 : “); DisplayVectorArray(resultArray, 3, 3, true); Console.WriteLine(); Console.WriteLine(“신경망 훈련 데모 종료”); Console.ReadLine(); } #endregion #region 데이터 준비하기 – PrepareData(sourceValueArray, seed, trainingValueArray, testValueArray) ///

/// 데이터 준비하기 ///

/// 소스 값 배열 /// 시드 /// 훈련 값 배열 /// 테스트 값 배열 private static void PrepareData ( double[][] sourceValueArray, int seed, out double[][] trainingValueArray, out double[][] testValueArray ) { Random random = new Random(seed); int rowCount = sourceValueArray.Length; int columnCount = sourceValueArray[0].Length; int trainingRowCount = (int)(rowCount * 0.80); int testRowCount = rowCount – trainingRowCount; trainingValueArray = new double[trainingRowCount][]; testValueArray = new double[testRowCount][]; double[][] copyValueArray = new double[sourceValueArray.Length][]; for(int i = 0; i < copyValueArray.Length; i++) { copyValueArray[i] = sourceValueArray[i]; } for(int i = 0; i < copyValueArray.Length; i++) { int randomIndex = random.Next(i, copyValueArray.Length); double[] temporaryArray = copyValueArray[randomIndex]; copyValueArray[randomIndex] = copyValueArray[i]; copyValueArray[i ] = temporaryArray; } for(int i = 0; i < trainingRowCount; i++) { trainingValueArray[i] = new double[columnCount]; for(int j = 0; j < columnCount; j++) { trainingValueArray[i][j] = copyValueArray[i][j]; } } for(int i = 0; i < testRowCount; i++) { testValueArray[i] = new double[columnCount]; for(int j = 0; j < columnCount; j++) { testValueArray[i][j] = copyValueArray[i + trainingRowCount][j]; } } } #endregion #region 벡터 배열 출력하기 - DisplayVectorArray(sourceVectorArray, valueCountPerRow, decimalCount, newLine) ///

/// 벡터 배열 출력하기 ///

/// 소스 벡터 배열 /// 행당 값 카운트 /// 소수점 카운트 /// 개행 여부 private static void DisplayVectorArray(double[] sourceVectorArray, int valueCountPerRow, int decimalCount, bool newLine) { int sourceVectorCount = sourceVectorArray.Length; for(int i = 0; i < sourceVectorCount; i++) { if(i % valueCountPerRow == 0) { Console.WriteLine(""); } Console.Write(sourceVectorArray[i].ToString("F" + decimalCount).PadLeft(decimalCount + 4) + " "); } if (newLine == true) { Console.WriteLine(""); } } #endregion #region 매트릭스 배열 출력하기 - DisplayMatrixArray(sourceMatrixArray, rowCount, decimalCount, newLine) ///

/// 매트릭스 배열 출력하기 ///

/// 소스 매트릭스 배열 /// 행 카운트 /// 소수점 카운트 /// 개행 여부 private static void DisplayMatrixArray(double[][] sourceMatrixArray, int rowCount, int decimalCount, bool newLine) { for(int i = 0; i < rowCount; i++) { Console.Write(i.ToString().PadLeft(3) + " : "); for(int j = 0; j < sourceMatrixArray[i].Length; j++) { if(sourceMatrixArray[i][j] >= 0d) { Console.Write(” “); } else { Console.Write(“-“); } Console.Write(Math.Abs(sourceMatrixArray[i][j]).ToString(“F” + decimalCount) + ” “); } Console.WriteLine(“”); } if(newLine == true) { Console.WriteLine(“”); } } #endregion } }

728×90

반응형

그리드형(광고전용)

키워드에 대한 정보 역 전파 알고리즘 소스

다음은 Bing에서 역 전파 알고리즘 소스 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.

이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!

사람들이 주제에 대해 자주 검색하는 키워드 오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다

  • 텐서플로우
  • 딥러닝
  • tensorflow
  • 텐서플로우 자격증
  • tensorflow certificate
  • 텐서플로우 강좌
  • 텐서플로우 2.0
  • 텐서플로우 강의
  • 딥러닝 강의
  • 딥러닝 입문
  • 비전공자 딥러닝
  • 비전공자 머신러닝
  • 머신러닝
  • 데이터분석
  • 비전공자 데이터 분석
  • 패스트캠퍼스
  • 런어데이
  • 딥러닝 수학
  • deep learning
  • 딥러닝 교육
  • 머신러닝 입문
  • 파이썬 데이터분석
  • 파이썬 강의
  • 파이썬 코딩
  • 텐서플로우 튜토리얼
  • 딥러닝 튜토리얼
  • 구글 텐서플로우
  • 딥러닝 자격증
  • 머신러닝 자격증
  • 텐서플로우 자격
  • 딥러닝 소개
  • 딥러닝 취업
  • 데이터분석 취업
  • 데이터분석 이직
  • 패스트캠퍼스 딥러닝
  • 패스트캠퍼스 데이터분석
  • python 강의
  • python 강좌
  • 파이썬 강좌
  • 딥러닝 강좌
  • 머신러닝 강좌
  • 머신러닝 수학
  • 캐글
  • kaggle
  • 자연어처리
  • 시계열분석
  • 테디노트
  • 인공지능강의
  • 데이터 취업
  • 오늘코드
  • 생활코딩

오차역전파 #(Backprogation)의 #개념을 #쉽게 #이해해 #봅시다


YouTube에서 역 전파 알고리즘 소스 주제의 다른 동영상 보기

주제에 대한 기사를 시청해 주셔서 감사합니다 오차역전파 (Backprogation)의 개념을 쉽게 이해해 봅시다 | 역 전파 알고리즘 소스, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.

See also  모세 혈관 터짐 | 그동안 몰랐던 모세혈관의 진실 [무엇이든 물어보세요] 20191014 97 개의 자세한 답변

Leave a Comment