미리 말씀드리지만 이 글은 좀 깁니다. 그리고 수식이 많아서 언뜻 보면 너무 어렵게 느껴질 수 있습니다. 하지만 이 글을 끝까지 인내하시면서 읽어내려가신다면 분명 오차 역전파(error backpropagation)가 무엇인지에 대한 지식과 더 나아가서 딥러닝의 본질에 대해 통찰력을 얻으실 수 있을 것입니다. 수식들을 직접 손으로 쓰시면서 살펴보신다면, 이해하는데 큰 도움이 될 것입니다.
"인내는 쓰다. 그러나 그 열매는 달다."
오차 역전파는 딥러닝 모델을 훈련시키는데 있어서 가장 흔히 사용되는 방법입니다. 오늘은 간단한 예를 통해 실제로 딥러닝 모델이 어떻게 훈련되는지에 대해서 살펴보고자 합니다. 저는 이 글을 작성하는데 있어서 [1]을 주로 참고했습니다.
다음과 같은 간단한 네트워크가 있다고 가정해봅시다. 단 하나의 은닉층(hidden layer)이 있는 인공신경망(artificial neural network)입니다.

w1, w2, w3,...,w8 까지 총 8개의 가중치(weight)가 훈련되어야합니다. 문제를 단순화시키기 위해 편향(bias)은 포함시키지 않았습니다. 단 하나의 훈련 데이터 샘플로 이 네트워크를 훈련시켜보겠습니다. 그 샘플은 (0.05, 0.1)이고 타겟값은 (0.01, 0.99)입니다. 그리고 가중치들은 다음과 같이 초기화시켜주겠습니다.

우선 이러한 상황에서 훈련 데이터를 네트워크에 통과시키면 어떤 출력값이 나오는지 살펴보도록 하겠습니다. 바로 타겟값인 (0.01, 0.99)와 비슷한 값이 나오면 좋겠지만, 확률적으로 그럴리는 거의 없습니다.
먼저 은닉층의 첫번째 노드의 net input은 얼마인지 구해보겠습니다. net input이라는 것은 입력값과 그와 연관된 가중치와 편향을 곱해서 더한 것을 의미합니다.
neth1=i1⋅w1+i2⋅w2=0.05⋅0.15+0.1⋅0.2=0.0275
이것을 로지스틱(logistic) 함수로 활성화(activation)시켜준 것을 저는 outh1이라고 표기하겠습니다. 로지스틱 함수는 입력값에 관계없이 항상 0과 1사이의 값을 출력해주는 함수입니다.
y=11+e−x
그러면 outh1은 다음과 같이 계산됩니다.
outh1=11+e−neth1=11+e−0.0275=0.5069
유효숫자 4개만 쓰겠습니다. 시간적 여유가 있는 분들은 손으로 수식 전개를 따라가시면서 이해하시는 것을 권해드립니다. 이 과정은 손으로 몇번 따라해봐야 자기 것이 될 수 있습니다.
이번에는 neth2와 outh2를 계산해보겠습니다.
neth2=i1⋅w3+i2⋅w4=0.05⋅0.25+0.1⋅0.3=0.0425
outh2=11+e−neth2=11+e−0.0425=0.5106
이제 outh1과 outh2는 다음 층의 인풋이 됩니다. neto1과 outo1을 구하면,
neto1=outh1⋅w5+outh2⋅w6=0.5069⋅0.35+0.5106⋅0.4=0.3817
outo1=11+e−neto1=11+e−0.3817=0.5943
이 됩니다. 마찬가지로 neto2와 outo2를 구하겠습니다.
neto2=outh1⋅w7+outh2⋅w8=0.5069⋅0.45+0.5106⋅0.5=0.4834
outo2=11+e−neto2=11+e−0.4834=0.6186
이제 outo1, outo2를 타겟값과 비교해서 오차를 계산해보겠습니다. 총 오차(total eror)는 오차 제곱의 합, 즉 다음과 같은 식으로 계산하도록 하겠습니다.
Etotal=Eo1+Eo2=12(targeto1−outo1)2+12(targeto2−outo2)2
앞에 1/2는 이후에 미분을 할때 계산하기 용이하도록 붙여준 것입니다. 뒷부분에 가시면 저절로 이해가 되실 것입니다. 자, 그럼 지금 상황에서 오차는 얼마인지 계산해보겠습니다.
Etotal=Eo1+Eo2=12(0.01−0.5943)2+12(0.99−0.6186)2=0.2397
0.2397의 오차가 계산되었습니다. 아직 출력값인 (0.5943, 0.6186)과 타겟값인 (0.01, 0.99) 사이에는 차이가 많이 있습니다. 우리의 목적은 이 오차가 0에 가까워지도록 네트워크의 가중치를 업데이트시켜가는 것입니다.
지금부터 출력단에서 가까운 w5,w6,w7,w8부터 어떻게 갱신해가는지 확인해보도록 하겠습니다. 핵심은 경사감소법 또는 경사하강법을 이용한다는 점입니다.
w5 업데이트 과정
Etotal 함수의 최소값은 각 가중치로 편미분했을 때 0이 되는 지점일 것입니다. 만약 현재의 가중치가 그 최소값을 갖게하는 위치에서 왼쪽에 있다면 오른쪽으로 가도록 보정하면 되는 것이고, 오른쪽에 있다면 왼쪽으로 가도록 보정하면 되는 것이죠. 아래 그림을 참고하세요(설명을 위해 8차원의 그래프를 2차원의 그래프로 단순화시켜서 그렸습니다).

최소값을 갖게 하는 점에서 가중치가 왼쪽에 있다면 그때 편미분한 결과는 음수일 것입니다. 따라서 그것에 마이너스를 붙여서 현재의 가중치에 더해준다면 오른쪽으로 이동할 것입니다. 이동하는 정도를 정해주기 위해서 학습률(learning rate) α를 곱해서 더해줍니다. 학습률이 크면 한번에 많이 움직이고, 작으면 적게 움직입니다. 이것을 식으로 나타내면 다음과 같습니다.
w5+=w5−α∂Etotal∂w5
여기서 w5+는 갱신된 w5를 의미합니다.

이러한 방식으로 가중치를 갱신하기 때문에 w5를 갱신하기 위해서는 ∂Etotal∂w5를 계산해줘야 합니다. Etotal을 w5에 대해 편미분해야하는데 Etotal 대한 식을 살펴보면 w5가 딱 보이지 않습니다. w5에 대한 식으로 바꿀 수는 있겠지만 너무 복잡해집니다. 따라서 체인 룰(chain rule)을 이용해서 ∂Etotal∂w5을 다음과 같이 바꿔줍니다.
∂Etotal∂w5=∂Etotal∂outo1×∂outo1∂neto1×∂neto1∂w5
위 식의 의미는 다음 그림과 같습니다. 오차를 w5까지 역전파시켜가는 것입니다.

먼저 ∂Etotal∂outo1부터 구해보면,
∂Etotal∂outo1=−(targeto1−outo1)=−(0.01−0.5943)=0.5843
이 됩니다. 그 다음에 ∂outo1∂neto1를 구해보겠습니다. 로지스틱 함수 y=11+e−x의 미분은
dydx=y(1−y)
이므로,
∂outo1∂neto1=outo1(1−outo1)=0.5943(1−0.5943)=0.2411
입니다. 또한 ∂neto1∂w5는
∂neto1∂w5=outh1=0.5069
이 됩니다. 이것들을 모두 곱하면,
∂Etotal∂w5=0.5843×0.2411×0.5069=0.0714
이 되고, 이것을 가지고 우리는 w5를 업데이트시킬 수 있습니다. 학습률 α은 0.5로 해주겠습니다.
w5+=w5−α∂Etotal∂w5=0.35−0.5⋅0.0714=0.3143
0.35였던 w5가 0.3143으로 갱신된 것입니다. 타겟값 targeto1=0.01에 비해서는 너무 큰 outo1=0.5943을 출력했었기 때문에 그것에 관여하는 w5가 작아진 것은 긍정적이라고 볼 수 있습니다. 그런데 바로 갱신시키지는 않고 모든 가중치의 갱신값을 구한 다음에 갱신시킵니다.
w6 업데이트 과정
이번에는 w6의 업데이트 과정을 살펴보겠습니다. w5와 같은 원리로 갱신되니 빠르게 설명하겠습니다. w6을 갱신하기 위해서는 이것을 풀어야합니다.
∂Etotal∂w6=∂Etotal∂outo1×∂outo1∂neto1×∂neto1∂w6
첫번째 인수와 두번째 인수는 이미 구한 값들이므로 세번째 인수만 계산하겠습니다.
∂neto1∂w6=outh2=0.5106
따라서,
∂Etotal∂w6=∂Etotal∂outo1×∂outo1∂neto1×∂neto1∂w6=0.5843×0.2411×0.5106=0.07193
이 됩니다. 이것을 가지고 w6를 갱신하면 다음과 같이 됩니다.
w6+=w6−α∂Etotal∂w6=0.4−0.5⋅0.07193=0.364
0.4였던 w6이 0.364로 갱신되었습니다. w6도 outo1에 관여하기 때문에 작아진 것은 긍정적입니다.
w7 업데이트 과정
w7을 갱신하기 위해서는 다음 식을 풀어야 합니다.
∂Etotal∂w7=∂Etotal∂outo2×∂outo2∂neto2×∂neto2∂w7
하나씩 풀어가면,
∂Etotal∂outo2=−(targeto2−outo2)=−(0.99−0.6186)=−0.3714
∂outo2∂neto2=outo2(1−outo2)=0.6186(1−0.6186)=0.2359
∂neto2∂w7=outh1=0.5069
이 되고, 이것들을 모두 곱하면
∂Etotal∂w7=∂Etotal∂outo2×∂outo2∂neto2×∂neto2∂w7=−0.3714×0.2359×0.5069=−0.04441
이 됩니다. 따라서 w7은 다음과 같이 갱신됩니다.
w7+=w7−α∂Etotal∂w7=0.45−0.5⋅(−0.04441)=0.4722
0.45였던 w7이 0.4722로 커졌습니다. targeto2=0.99에 비해 outo2=0.6186이 작았던 상황에서 outo2 계산에 관여하는 w7이 커진 것은 긍정적입니다.
w8 업데이트 과정
w8을 갱신하기 위해서는,
∂Etotal∂w8=∂Etotal∂outo2×∂outo2∂neto2×∂neto2∂w8
를 풀어야하겠죠. 첫번째, 두번째 인수는 이미 위에서 구했으므로 세번째 인수만 구하면 됩니다.
∂neto2∂w8=outh2=0.5106
이므로,
∂Etotal∂w8=∂Etotal∂outo2×∂outo2∂neto2×∂neto2∂w8=−0.3714×0.2359×0.5106=−0.04474
이 됩니다. 또한 w8은 다음과 같이 업데이트 되겠죠.
w8+=w8−α∂Etotal∂w8=0.5−0.5⋅(−0.04474)=0.5224
0.5였던 w8이 0.5224로 커진 것도 긍정적인 일입니다. 왜냐하면 w8도 outo2의 연산에 관여하기 때문입니다.
여기까지 오시느라 수고하셔습니다. 잠시 숨을 고르고 가겠습니다. 지금까지 우리는 은닉층과 출력층 사이의 가중치들을 갱신해왔습니다. 이제 더 나아가서 입력층과 은닉층 사이의 가중치들 w1,w2,w3,w4를 갱신할 차례입니다. 자, 다시 펜을 손에 쥐고 가볼까요?
w1 업데이트 과정
이번에도 역시 ∂Etotal∂w1를 구해야 합니다. 이것을 체인 룰을 이용해서 다음과 같이 바꿔주겠습니다.
∂Etotal∂w1=∂Etotal∂outh1×∂outh1∂neth1×∂neth1∂w1 ... (식1)
여기서 두번째 인수와 세번째 인수는 쉽게 구할 수 있습니다. 그러나 첫번째 인수는 당장 구하기는 어렵습니다. 그래서 첫번째 인수를 다음과 같이 분리시키겠습니다.
∂Etotal∂outh1=∂Eo1∂outh1+∂Eo2∂outh1
각각의 항은 또 다음과 같이 분해가 가능합니다.
∂Etotal∂outh1=∂Eo1∂outh1+∂Eo2∂outh1=∂Eo1∂neto1×∂neto1∂outh1+∂Eo2∂neto2×∂neto2∂outh1
이것은 또 다음과 같이 분해할 수 있습니다.
∂Etotal∂outh1=∂Eo1∂outh1+∂Eo2∂outh1=∂Eo1∂neto1×∂neto1∂outh1+∂Eo2∂neto2×∂neto2∂outh1=(∂Eo1∂outo1×∂outo1∂neto1)×∂neto1∂outh1+(∂Eo2∂outo2×∂outo2∂neto2)×∂neto2∂outh1
이제 모두 계산하기 쉽게 되었습니다. 계산해야할 것은 많지만요. 이것을 식1에 대입시키면, 다음과 같이 됩니다.
∂Etotal∂w1=[∂Eo1∂outo1×∂outo1∂neto1×∂neto1∂outh1+∂Eo2∂outo2×∂outo2∂neto2×∂neto2∂outh1]×∂outh1∂neth1×∂neth1∂w1
이 식이 의미하는 바는 다음 그림과 같습니다.

이런 식으로 에러를 역전파시키는 방법으로 가중치를 보정하기 때문에 에러 역전파라고 불리는 것입니다. 모든 값을 구해서 대입해서 계산해주겠습니다.
∂Etotal∂w1=[0.5843×0.2411×0.35+(−0.3714)×0.2359×0.45]×0.25×0.05=0.0001235
이 값을 가지고 w1을 갱신시켜줍니다.
w1+=w1−α∂Etotal∂w1=0.15−0.5⋅(0.0001235)=0.1499
0.15였던 w1이 0.1499로 갱신되었습니다. w5,w6,w7,w8에 비해 변화한 정도가 상당히 작죠. 이런 이유로 인공신경망에는 경사소멸(vanishing gradient) 문제가 있다고 평가되는 것입니다.
w2 업데이트 과정
w2를 업데이트하기 위해서는 아래 식을 풀어야 합니다.
∂Etotal∂w2=∂Etotal∂outh1×∂outh1∂neth1×∂neth1∂w2
첫번째 인수와 두번째 인수의 값은 방금 모두 구했으므로 세번째 인수에 대해서만 푼 후에 대입시키면 됩니다.
∂neth1∂w2=i2=0.1
이므로,
∂Etotal∂w2=0.0099×0.25×0.1=0.0002475
가 됩니다. 이것을 가지고 w2를 갱신시키겠습니다.
w2+=w2−α∂Etotal∂w2=0.2−0.5⋅(0.0002475)=0.1999
0.2였던 w2가 0.1999로 갱신되었습니다. 마찬가지로 아주 미세하게 조정되었습니다.
w3 업데이트 과정
w3을 구하는 것도 마찬가지입니다. 한 번에 전개된 식을 적고 넘어가겠습니다.
∂Etotal∂w3=[∂Eo1∂outo1×∂outo1∂neto1×∂neto1∂outh2+∂Eo2∂outo2×∂outo2∂neto2×∂neto2∂outh2]×∂outh2∂neth2×∂neth2∂w3
모든 값을 계산한 후에 대입해서 계산해주면 다음과 같은 값이 나옵니다.
∂Etotal∂w3=[0.5843×0.2411×0.4+(−0.3714)×0.2359×0.5]×0.2499×0.05=0.0001567
이것을 가지고 w3을 갱신해주겠습니다.
w3+=w3−α∂Etotal∂w3=0.25−0.5⋅(0.0001567)=0.2499
0.25였던 w3이 0.2499로 갱신되었습니다.
w4 업데이트 과정
마지막 가중치입니다. 이를 업데이트시키기 위해서는 아래 식을 풀어야 합니다.
∂Etotal∂w4=∂Etotal∂outh2×∂outh2∂neth2×∂neth2∂w4
여기서 첫번째 인수와 두번째 인수는 w3을 다루는 과정에서 이미 구했으므로, 세번째 인수의 값만 구하면 됩니다.
∂neth2∂w4=i2=0.1
이네요. 모든 인수를 대입해서 곱해주면 다음과 같이 됩니다.
∂Etotal∂w4=0.01255×0.2499×0.1=0.0003136
이것을 가지고 w4를 갱신하겠습니다.
w4+=w4−α∂Etotal∂w4=0.3−0.5⋅0.0003136=0.2998
0.3이었던 w4가 0.2998로 조정되었습니다.
드디어 모든 가중치를 한번씩 업데이트 시켰습니다. 여기까지도 읽어내려오신 분들의 끈기에 박수를 드립니다. 이제 업데이트된 가중치를 가진 네트워크에 다시 훈련 데이터를 통과시켜보겠습니다. 오차가 줄어들었는지 확인해봅시다.

neth1=0.05⋅0.1499+0.1⋅0.1999=0.02749
outh1=11+e−0.02749=0.5069
neth2=0.05⋅0.2499+0.1⋅0.2998=0.04248
outh2=11+e−0.04248=0.5106
neto1=0.5069⋅0.3143+0.5106⋅0.364=0.3452
outo1=11+e−0.3452=0.5855
neto2=0.5069⋅0.4722+0.5106⋅0.5224=0.5061
outo2=11+e−0.5061=0.6239
이므로, Etotal을 구하면 다음과 같은 값이 나옵니다.
Etotal=12(0.01−0.5855)2+12(0.99−0.6239)2=0.2326
가중치가 초기값으로 설정되었을 때의 오차보다는 작아졌음을 알 수 있습니다. 0.2397에서 0.2326으로 0.0071만큼 줄어들었네요. 이 훈련 과정을 여러번 반복하면 가중치들은 계속해서 갱신되어갈 것이고, 점차 Etotal은 0에 가까워지게 될 것입니다. 물론 제대로 훈련되었을 경우에 그렇습니다.
드디어 끝입니다. 따라오시느라 정말 수고 많으셨습니다. 지루할 수 있는 글을 끝까지 읽으신 분들께 감사드립니다. 이번에는 저 스스로도 칭찬해주고 싶네요. 수식을 일일이 쓰느라 꽤나 고생했습니다.^^; 오차 역전파에 대해서 이해가 되셨나요? 도움이 되셨다면 공감과 댓글을 남겨주시길 부탁드리며 이 글을 맺겠습니다.
bskyvision의 추천글 ☞
딥러닝 알고리즘의 대세, 컨볼루션 신경망(convolutional neural network, CNN)
<참고자료>
[1] https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/, Matt Mazur, "A Step by Step Backpropagation Example"
[2] https://www.mathworks.com/help/deeplearning/ug/choose-neural-network-input-output-processing-functions.html;jsessionid=e1d5d9c3e80282fd1084cde13e45, MathWorks, "Choose Neural Network Input-Output Processing Functions" => net input 정의 부분 참고함.
'Research > ML, DL' 카테고리의 다른 글
단순회귀분석 vs 다항회귀분석 vs 다중회귀분석 (0) | 2022.09.05 |
---|---|
이진 분류기 성능 평가방법 AUC(area under the ROC curve)의 이해 (6) | 2021.04.09 |
정규화(normalization)와 표준화(standardization), 머신러닝 성능 향상을 위한 필수 단계 (4) | 2020.07.13 |
딥러닝 손실 함수(loss function) 정리: MSE, MAE, binary/categorical/sparse categorical crossentropy (4) | 2020.06.24 |
배치(batch)와 에포크(epoch)란? (5) | 2020.06.11 |
ground-truth 라벨을 얻기 어려운 경우 대안, pseudo 라벨 (0) | 2020.04.15 |
손실함수(loss)와 평가지표(metric)란? 그 차이는? (0) | 2020.04.07 |
hold-out 교차검증과 k-fold 교차검증 (2) | 2020.03.16 |