logo

[딥러닝] 역전파 알고리즘

역전파 알고리즘(Backpropagation algorithm)은 신경망 학습에 핵심적인 역할을 하는 메커니즘입니다. 이 알고리즘은 신경망의 가중치와 바이어스를 조정하는 과정에 사용되며, 이를 통해 신경망의 성능을 최적화합니다. 역전파는 특히 심층 신경망의 학습에 있어 필수적인 알고리즘으로, 신경망의 출력과 실제 목표값 사이의 오차를 감소시키는 것이 목적입니다.

 

역전파 알고리즘의 정의 및 필요성

역전파 알고리즘은 신경망 학습에서 중요한 개념인 오차 역전파 방식을 사용합니다. 신경망을 학습시키기 위해서는, 먼저 네트워크에 입력 데이터를 제공하고 순방향 전파(forward propagation) 과정을 통해 출력값을 얻습니다. 이 출력값과 실제 목표값 사이의 차이, 즉 오차를 계산한 후, 이 오차를 기반으로 신경망의 가중치와 바이어스를 조정해야 합니다. 이때, 오차를 신경망의 각 층을 거슬러 올라가며 가중치에 대한 오차의 영향을 계산하고, 이를 통해 가중치를 업데이트하는 과정이 역전파입니다.

 

순방향 전파와의 관계

순방향 전파는 입력 데이터로부터 시작해 출력층까지 데이터가 흐르는 과정을 말합니다. 이 과정에서 각 층의 뉴런은 이전 층의 출력을 입력으로 받아, 활성화 함수를 통과한 결과를 다음 층으로 전달합니다. 순방향 전파를 통해 얻은 출력값은 이후 역전파 과정의 출발점이 됩니다. 즉, 역전파는 순방향 전파를 통해 계산된 출력값과 실제 목표값 사이의 오차를 기반으로 실시됩니다.

Python과 PyTorch를 사용한 간단한 역전파 알고리즘 예시는 다음과 같습니다. 여기서는 PyTorch의 자동 미분 기능을 이용해 역전파를 구현할 수 있음을 보여줍니다.

import torch

# 입력 변수와 가중치, 목표값을 정의
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
w = torch.tensor([0.1, 0.2, 0.3], requires_grad=True)
y_true = torch.tensor([0.0])

# 순방향 전파: 가중치와 입력의 내적을 계산하고, 손실 함수로 오차 계산
y_pred = x @ w  # @는 행렬 곱셈을 의미
loss = (y_pred - y_true).pow(2).sum()

# 역전파: 손실 함수에 대한 가중치의 그래디언트 계산
loss.backward()

# 가중치 업데이트 (실제로는 학습률을 곱하여 업데이트하지만 여기서는 간략화)
w.data -= w.grad

# 그래디언트 초기화
w.grad.zero_()

이 코드는 PyTorch의 자동 미분 기능을 활용하여 역전파를 수행합니다. loss.backward() 호출은 역전파를 트리거하며, 이후 w.grad에 가중치의 기울기(gradient)가 저장됩니다. 이를 통해 가중치 업데이트가 가능해집니다. 이 과정을 반복하면서 신경망은 점진적으로 목표값에 가까운 출력을 생성하게 됩니다.

역전파 알고리즘은 신경망 학습의 효율성과 효과를 크게 향상시켰지만, 기울기 소실(vanishing gradient)이나 기울기 폭발(exploding gradient)과 같은 문제를 포함한 몇 가지 한계점이 있습니다. 이와 같은 문제들은 다양한 개선 방안, 예를 들어 정규화 기법, 변형된 활성화 함수, 그리고 학습률 조정 기법 등을 통해 해결되고 있습니다.


역전파(Backpropagation) 알고리즘의 핵심은 신경망 내에서 가중치(weights)와 편향(biases)의 그래디언트(gradient)를 계산하여, 학습하는 동안 이를 통해 네트워크의 가중치를 조정하는 데 있습니다. 이 절차는 주로 연쇄법칙(Chain Rule)을 사용한 미분을 기반으로 합니다. 여기서 연쇄법칙이란 합성함수의 미분에 관한 것으로, 여러 함수가 결합된 함수의 도함수를 찾는 데 사용됩니다.

 

연쇄법칙(Chain Rule)의 이해

역전파에서는 연쇄법칙을 통해 출력층에서 입력층 방향으로 오차를 전파하면서 각 층의 가중치에 대한 손실 함수의 변화율(gradient)를 계산합니다. 간단히 말해, 어떤 함수가 y=g(x)y=g(x)이고 x=f(u)x=f(u)일 때, uu에 대한 yy의 전체 변화율은 아래와 같이 계산됩니다.

dydu=dydxdxdu \frac{dy}{du} = \frac{dy}{dx} \cdot \frac{dx}{du}

이 공식은 더 복잡한 신경망에서도 층을 거슬러 오차를 계산할 때 적용됩니다.

 

가중치와 편향에 대한 그래디언트 계산

신경망의 각 층에서, 역전파는 주어진 층의 가중치와 편향의 손실 함수에 대한 그래디언트를 계산합니다. 이 계산은 모델이 예측한 출력과 실제 값 사이의 오차를 바탕으로 이루어집니다.

PyTorch를 사용한 간단한 예시를 통해 이 과정을 이해해보겠습니다. 이 예제에서는 신경망의 한 층에서 그래디언트 계산을 수행합니다.

import torch

# 임의의 데이터와 가중치, 편향을 정의
x = torch.tensor([-2., -1., 1., 2.], requires_grad=True)
y_true = torch.tensor([0., 1., 1., 0.])

# 간단한 신경망 가중치와 편향
w = torch.tensor([1.], requires_grad=True)
b = torch.tensor([0.], requires_grad=True)

# 순전파: 예측값 계산
y_pred = w * x + b

# 손실 함수 계산 (여기서는 MSE를 사용)
loss = (y_true - y_pred).pow(2).mean()

# 역전파: 그래디언트 계산
loss.backward()

# 가중치와 편향의 그래디언트 출력
print(f'Gradient of w: {w.grad}')
print(f'Gradient of b: {b.grad}')

이 코드는 간단한 선형 모델에서 손실(loss)에 대한 가중치(w)와 편향(b)의 그래디언트를 계산하는 과정을 보여줍니다. .backward() 호출을 통해 PyTorch는 자동으로 그래디언트를 계산하며, w.gradb.grad를 통해 각각의 그래디언트 값을 확인할 수 있습니다.

종합하자면, 역전파 알고리즘의 동작 원리는 손실 함수로부터 모델의 출력에 이르기까지의 경로를 거슬러 가면서 각 가중치에 대한 그래디언트를 계산하는 것입니다. 이 과정에서 연쇄법칙을 사용하여 각 미분의 곱을 통해 최종적인 그래디언트를 얻어내고, 이를 통해 네트워크의 가중치를 조정하게 됩니다.


역전파(Backpropagation) 알고리즘의 구현을 단층 신경망과 다층 신경망에서의 예시를 통해 설명하겠습니다. 이를 위해 PyTorch, 현재 가장 널리 사용되는 딥러닝 프레임워크 중 하나를 사용합니다.

 

필요한 라이브러리 임포트

먼저 PyTorch 라이브러리를 임포트합니다.

import torch
import torch.nn as nn
import torch.optim as optim
 

단층 신경망에서의 구현 예

단층 신경망은 입력층과 출력층만 존재하는 가장 간단한 신경망 구조입니다.

간단한 예로, 하나의 입력과 하나의 출력을 갖는 신경망을 구현해보겠습니다.

# 단층 신경망 정의
class SingleLayerNN(nn.Module):
    def __init__(self):
        super(SingleLayerNN, self).__init__()
        # 입력층과 출력층을 연결하는 선형계층 정의
        self.linear = nn.Linear(1, 1)

    def forward(self, x):
        # 선형 변환을 통한 예측값 계산
        return self.linear(x)

# 신경망 인스턴스 생성
model = SingleLayerNN()

# 손실 함수 및 최적화 기법 정의
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 샘플 데이터
x_train = torch.tensor([[1.0], [2.0], [3.0]])
y_train = torch.tensor([[2.0], [4.0], [6.0]])

# 학습 과정
for epoch in range(1000):
    optimizer.zero_grad()   # 기울기 초기화
    outputs = model(x_train) # 예측 값 계산
    loss = criterion(outputs, y_train) # 손실 계산
    loss.backward()         # 역전파 실행
    optimizer.step()        # 가중치 업데이트

    if (epoch+1) % 100 == 0:
        print(f'Epoch [{epoch+1}/1000], Loss: {loss.item():.4f}')
 

다층 신경망에서의 구현 예

다층 신경망은 입력층, 하나 혹은 그 이상의 은닉층, 그리고 출력층을 포함합니다. 은닉층을 포함하면 전체 네트워크에서 직접적으로 관찰되지 않는 내부 표현을 학습할 수 있습니다.

간단한 다층 신경망을 구현하여 볼겠습니다.

# 다층 신경망 정의
class MultiLayerNN(nn.Module):
    def __init__(self):
        super(MultiLayerNN, self).__init__()
        # 입력층과 은닉층을 연결하는 선형 계층
        self.linear1 = nn.Linear(1, 5)
        # 은닉층과 출력층을 연결하는 선형 계층
        self.linear2 = nn.Linear(5, 1)
        # 활성화 함수
        self.relu = nn.ReLU()

    def forward(self, x):
        # 첫 번째 선형 계층 후 활성화 함수 적용
        out = self.relu(self.linear1(x))
        # 출력 계층을 통한 예측값 계산
        out = self.linear2(out)
        return out

# 신경망 인스턴스 생성
model = MultiLayerNN()

# 손실 함수 및 최적화 기법 정의
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 샘플 데이터
x_train = torch.tensor([[1.0], [2.0], [3.0]])
y_train = torch.tensor([[2.0], [4.0], [6.0]])

# 학습 과정
for epoch in range(1000):
    optimizer.zero_grad()   # 기울기 초기화
    outputs = model(x_train) # 예측 값 계산
    loss = criterion(outputs, y_train) # 손실 계산
    loss.backward()         # 역전파 실행
    optimizer.step()        # 가중치 업데이트

    if (epoch+1) % 100 == 0:
        print(f'Epoch [{epoch+1}/1000], Loss: {loss.item():.4f}')

위 코드는 단층 신경망과 다층 신경망에서 역전파 알고리즘의 구현을 보여줍니다. 각각의 코드는 PyTorch의 구성 요소를 사용하여 신경망을 정의하고, 손실을 계산한 뒤, loss.backward() 메소드를 호출하여 역전파를 실행하고, optimizer.step()을 통해 가중치를 업데이트하는 과정을 수행합니다.


역전파 알고리즘의 한계와 개선 방향에는 주로 기울기 소실(Vanishing Gradient) 문제와 기울기 폭발(Exploding Gradient) 문제가 있습니다. 이 문제들은 신경망의 깊이가 깊어질수록 그 영향이 커지는 경향이 있습니다.

 

기울기 소실 문제(Vanishing Gradient problem)

기울기 소실 문제는 딥러닝 모델을 학습시킬 때 역전파 과정에서의 그라데이션(기울기)가 점점 작아져서 맨 앞쪽 레이어로 전달될 때 거의 사라지는 문제입니다. 이렇게 되면 네트워크의 초기 레이어의 가중치는 거의 업데이트되지 않게 됩니다. 이는 특히 sigmoid나 tanh 같은 활성화 함수를 사용할 때 자주 발생하는 문제입니다.

 

기울기 폭발 문제(Exploding Gradient problem)

반대로, 기울기 폭발 문제는 기울기가 너무 커져서 가중치 업데이트 시 네트워크가 불안정해지는 문제를 일컫습니다. 이 문제는 주로 RNN(Recurrent Neural Networks)에서 나타나는 경향이 있습니다.

 

해결 방법

그래디언트 클리핑 (Gradient Clipping)

기울기 폭발 문제를 해결하는 한 가지 방법은 그래디언트 클리핑입니다. 이 방법은 그래디언트의 크기가 특정 임계값을 초과하면 스케일링하여 기울기의 크기를 줄이는 방식입니다.

PyTorch 예시:
import torch

# 모델, 최적화 설정
model = ...  # 모델 선언
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# 가정: loss가 계산되었다고 할 때
loss.backward()  # 그래디언트 계산

# 그래디언트 클리핑 적용
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# 가중치 업데이트
optimizer.step()

가중치 초기화 방법

신경망의 가중치 초기화는 매우 중요한 단계로, 잘못된 초기화는 기울기 소실 또는 기울기 폭발 문제를 악화시킬 수 있습니다. 일반적으로 Xavier 초기화나 He 초기화 같은 방법을 사용하여 이 문제들을 완화할 수 있습니다.

PyTorch 예시:
  • Xavier 초기화 방법: 일반적으로 활성화 함수로 sigmoid나 tanh를 사용할 때 적합합니다.
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.layer1 = nn.Linear(in_features=..., out_features=...)
        nn.init.xavier_uniform_(self.layer1.weight)

model = MyModel()
  • He 초기화 방법: 일반적으로 활성화 함수로 ReLU를 사용할 때 적합합니다.
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.layer1 = nn.Linear(in_features=..., out_features=...)
        nn.init.kaiming_normal_(self.layer1.weight)

model = MyModel()

이러한 기술적 대응 방안 외에도 신경망 구조 자체를 변경하여 이러한 문제를 완화하는 방안들(예: LSTM, GRU 등 순환 신경망의 변형, ResNet 같은 포워드 연결을 추가한 네트워크)도 널리 채택되고 있습니다.

Previous
경사하강법