3-3. PyTorch: 자동 미분 (Autograd)

1. 자동 미분 (Autograd)의 이해

자동 미분(Autograd)은 딥러닝 프레임워크의 핵심 기능 중 하나로, 신경망의 학습 과정에서 가중치(weight)에 대한 손실 함수(loss function)의 기울기(gradient)를 자동으로 계산하는 기술입니다. 기울기는 손실 함수를 최소화하기 위해 각 가중치를 어떻게 조정해야 하는지를 알려주는 중요한 정보입니다. 수동으로 미분 계산을 하는 것은 복잡하고 시간 소모적이며, 오류가 발생하기 쉽습니다. Autograd는 이러한 과정을 자동화하여 딥러닝 모델 개발을 훨씬 효율적으로 만들어줍니다.

1) 자동 미분의 필요성: 경사 하강법 (Gradient Descent)

딥러닝 모델은 주어진 데이터를 가장 잘 설명하는 가중치를 찾는 과정을 통해 학습합니다. 이 과정은 손실 함수를 최소화하는 것을 목표로 하며, 이때 사용되는 주요 알고리즘 중 하나가 경사 하강법입니다. 경사 하강법은 손실 함수의 기울기를 사용하여 가중치를 업데이트합니다.

경사 하강법 설명 뒤

위 그림은 경사 하강법의 개념을 시각적으로 보여줍니다. 손실 함수는 복잡한 형태의 "지형"으로 볼 수 있으며, 우리의 목표는 이 지형에서 가장 낮은 지점, 즉 최소 손실점(global minimum)을 찾는 것입니다. 경사 하강법은 현재 위치에서의 기울기 정보를 활용하여 가장 빠르게 손실이 감소하는 방향으로 이동합니다. 즉, 기울기가 가리키는 반대 방향으로 가중치를 조정하여 손실을 줄여나가는 것입니다. 이때, Autograd는 각 가중치에 대한 손실 함수의 기울기를 자동으로 계산하여 경사 하강법의 핵심적인 역할을 수행합니다.

2) 자동 미분과 역전파 (Backpropagation)

Autograd는 역전파(Backpropagation) 알고리즘을 기반으로 합니다. 역전파는 신경망의 출력에서 시작하여 각 층을 거슬러 올라가면서 기울기를 계산하는 과정입니다.

  • 순전파(Forward Pass): 입력 데이터가 신경망을 통과하여 최종 출력이 계산되는 과정입니다. 이 과정에서 각 층의 활성화 함수(activation function)를 거치며, 중간 출력값들이 저장됩니다.
  • 역전파(Backward Pass): 순전파 과정에서 저장된 중간 출력값들을 이용하여, 손실 함수에 대한 각 가중치의 기울기를 계산합니다. Autograd는 이 과정에서 연쇄 법칙(chain rule)을 사용하여 각 층의 기울기를 효율적으로 계산합니다.

연쇄 법칙은 합성 함수의 미분법으로, 복잡한 함수의 미분을 간단한 함수의 미분으로 분해하여 계산할 수 있게 해줍니다. 신경망은 여러 개의 합성 함수로 구성되어 있으므로, 연쇄 법칙을 통해 각 층의 기울기를 효율적으로 계산할 수 있습니다.

2. PyTorch의 Autograd: 핵심 개념

PyTorch에서 Autograd는 torch.autograd 패키지를 통해 제공됩니다. 핵심적인 개념은 Tensor 객체의 requires_grad, grad 속성, 그리고 backward() 함수입니다.

1) requires_grad: 기울기 계산 여부 설정

requires_grad는 텐서가 기울기를 계산해야 하는지 여부를 결정하는 중요한 플래그입니다.

  • requires_grad = True: 해당 텐서에 대한 모든 연산은 기울기 계산을 추적합니다. 즉, 이 텐서가 모델의 가중치로 사용될 경우, Autograd는 해당 가중치에 대한 기울기를 계산하고 저장합니다.
  • requires_grad = False: 해당 텐서에 대한 연산은 기울기 계산에서 제외됩니다. 이 텐서는 모델의 입력 데이터, 또는 학습되지 않는 파라미터(예: 상수, 사전 훈련된 가중치) 등에 사용될 수 있습니다.

requires_grad는 텐서를 생성할 때 또는 이미 생성된 텐서의 속성을 변경하여 설정할 수 있습니다.

import torch

# requires_grad=True로 설정하여 텐서 생성
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(x)

# requires_grad 설정 변경
y = torch.tensor([4.0, 5.0, 6.0])
y.requires_grad_(True)  # in-place operation
print(y)

# requires_grad=False로 설정
z = torch.tensor([7.0, 8.0, 9.0], requires_grad=False)
print(z)

2) grad: 기울기 저장

grad 속성은 텐서에 대한 기울기를 저장합니다. Autograd는 backward() 함수를 호출하여 기울기를 계산한 후, requires_grad=True로 설정된 텐서의 grad 속성에 기울기 값을 저장합니다.

import torch

# requires_grad=True로 설정된 텐서 생성
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 연산 수행
y = x * 2
z = y.mean()

# z에 대한 x의 기울기 계산
z.backward()

# x의 기울기 출력
print(x.grad)  # tensor([0.6667, 0.6667, 0.6667])

위 예제에서 xrequires_grad=True로 설정되었으므로, z.backward()를 호출하면 xgrad 속성에 z에 대한 x의 기울기가 저장됩니다.

3) backward(): 기울기 계산

backward() 함수는 손실 함수에 대한 기울기를 계산하는 핵심 함수입니다. 이 함수는 requires_grad=True로 설정된 텐서에 대한 연산을 추적하고, 역전파 알고리즘을 적용하여 기울기를 계산합니다.

import torch

# 텐서 생성
x = torch.tensor([[1.0, 2.0], [3.0, 4.0]], requires_grad=True)

# 연산 정의
y = x * 2 + 1
z = y.mean()

# 기울기 계산
z.backward()  # z.backward() is equivalent to z.backward(torch.tensor(1.0))
# z.backward(torch.tensor([1.0, 1.0])) # 오류 발생 (z는 스칼라여야 함)

# x의 기울기 출력
print(x.grad)

backward() 함수를 호출할 때, 기울기를 계산할 텐서(일반적으로 손실 함수)는 스칼라 값이어야 합니다. 만약 텐서가 스칼라가 아닌 경우, backward(gradient)와 같이 gradient 인자를 전달해야 합니다. gradient는 계산될 기울기의 초기값으로, 일반적으로 1.0으로 설정됩니다.

4) no_grad(): 기울기 계산 비활성화

torch.no_grad() 컨텍스트 매니저는 기울기 계산을 일시적으로 비활성화하는 데 사용됩니다. 이는 모델의 추론(inference) 단계나 기울기 계산이 필요 없는 특정 연산에 유용합니다.

import torch

# 모델 정의 (예시)
class MyModel(torch.nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.linear = torch.nn.Linear(10, 1)

    def forward(self, x):
        return self.linear(x)

# 모델 인스턴스 생성
model = MyModel()

# 입력 데이터 생성
input_data = torch.randn(1, 10)

# 추론 단계 (기울기 계산 비활성화)
with torch.no_grad():
    output = model(input_data)
    # 기울기 계산 X
    print(output)

# 학습 단계 (기울기 계산 활성화)
output = model(input_data)
loss = output.mean()
loss.backward()  # 기울기 계산 O

5) detach(): 텐서 분리

detach() 함수는 텐서를 계산 그래프에서 분리하여 기울기 계산에 영향을 받지 않도록 합니다. 이는 특정 텐서의 기울기를 계산하지 않고, 해당 텐서를 다른 연산에 사용하고 싶을 때 유용합니다.

import torch

# requires_grad=True로 설정된 텐서 생성
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 연산 수행
y = x * 2
z = y.detach() # z는 계산 그래프에서 분리됨
w = z + 1

# z에 대한 w의 기울기 계산
w.backward(torch.ones_like(w))

# x의 기울기 출력
print(x.grad) # tensor([2., 2., 2.])
print(z.requires_grad) # False
print(w.requires_grad) # False

위 예제에서 z = y.detach()를 통해 zy와는 별개의 텐서가 됩니다. 따라서 w.backward()를 호출해도 x의 기울기에 영향을 미치지 않습니다.

3. 자동 미분 활용 예시

1) 간단한 선형 회귀

다음은 자동 미분을 사용하여 간단한 선형 회귀 모델을 학습하는 예시입니다.

import torch

# 1. 데이터 생성
X = torch.tensor([[1.0], [2.0], [3.0]])  # 입력 데이터
y = torch.tensor([[2.0], [4.0], [5.0]])  # 정답 (label)

# 2. 모델 정의 (가중치와 편향 초기화)
w = torch.tensor([[0.0]], requires_grad=True)  # 가중치, requires_grad=True
b = torch.tensor([[0.0]], requires_grad=True)  # 편향, requires_grad=True

# 3. 학습 루프
learning_rate = 0.01
epochs = 100

for epoch in range(epochs):
    # 3.1 순전파: 예측값 계산
    y_pred = X @ w + b  # @는 행렬 곱셈 연산자

    # 3.2 손실 계산: MSE (Mean Squared Error)
    loss = ((y_pred - y)**2).mean()

    # 3.3 역전파: 기울기 계산
    loss.backward()  # w.grad와 b.grad에 기울기 저장

    # 3.4 경사 하강법: 가중치 및 편향 업데이트
    with torch.no_grad():  # 기울기 계산을 하지 않음 (update 시에는 기울기 계산 X)
        w -= learning_rate * w.grad  # 가중치 업데이트
        b -= learning_rate * b.grad  # 편향 업데이트

        # 기울기 초기화 (다음 epoch를 위해)
        w.grad.zero_()  # w.grad = torch.zeros_like(w.grad)
        b.grad.zero_()  # b.grad = torch.zeros_like(b.grad)

    # 4. 에폭마다 손실 출력 (선택 사항)
    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')

# 5. 학습된 가중치 및 편향 출력
print(f'Learned weight: {w.item():.4f}')
print(f'Learned bias: {b.item():.4f}')

# 6. 예측 (선택 사항)
X_test = torch.tensor([[4.0]])
y_test_pred = X_test @ w + b
print(f'Prediction for X_test: {y_test_pred.item():.4f}')

위 예제는 다음과 같은 주요 단계를 포함합니다.

  1. 데이터 생성: 입력 데이터 X와 정답 y를 텐서로 정의합니다.
  2. 모델 정의: 가중치 w와 편향 brequires_grad=True로 초기화합니다.
  3. 학습 루프: 각 에폭(epoch)마다 순전파, 손실 계산, 역전파, 가중치 업데이트 과정을 반복합니다.
    • 순전파: 모델의 예측값 y_pred를 계산합니다.
    • 손실 계산: 예측값과 정답 간의 손실(MSE)을 계산합니다.
    • 역전파: loss.backward()를 호출하여 가중치와 편향에 대한 기울기를 계산합니다.
    • 경사 하강법: wb를 기울기의 반대 방향으로 이동시켜 가중치를 업데이트합니다. 이때, torch.no_grad() 컨텍스트 매니저를 사용하여 기울기 계산을 비활성화합니다.
    • 기울기 초기화: w.grad.zero_()b.grad.zero_()를 사용하여 다음 에폭을 위해 기울기를 초기화합니다.
  4. 학습된 가중치 및 편향 출력: 학습된 가중치와 편향 값을 출력합니다.
  5. 예측 (선택 사항): 학습된 모델을 사용하여 새로운 입력 데이터에 대한 예측을 수행합니다.

2) 다층 퍼셉트론 (MLP)

다음은 PyTorch의 nn 모듈을 사용하여 다층 퍼셉트론(MLP) 모델을 학습하는 예시입니다. 이 예시에서는 Autograd를 사용하여 모델의 가중치를 업데이트합니다.

import torch
import torch.nn as nn

# 1. 데이터 생성
X = torch.randn(100, 10)  # 100개의 샘플, 각 샘플은 10개의 특징을 가짐
y = torch.randn(100, 1)   # 각 샘플에 대한 정답 (label)

# 2. 모델 정의 (nn.Module 상속)
class MLP(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)  # 첫 번째 fully connected layer
        self.relu = nn.ReLU()                          # 활성화 함수
        self.fc2 = nn.Linear(hidden_size, output_size) # 두 번째 fully connected layer

    def forward(self, x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.fc2(out)
        return out

# 3. 모델 인스턴스 생성
input_size = 10
hidden_size = 5
output_size = 1
model = MLP(input_size, hidden_size, output_size)

# 4. 손실 함수와 옵티마이저 정의
criterion = nn.MSELoss()  # MSE (Mean Squared Error) 손실 함수
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # SGD (Stochastic Gradient Descent) 옵티마이저

# 5. 학습 루프
epochs = 100
for epoch in range(epochs):
    # 5.1 순전파
    y_pred = model(X)

    # 5.2 손실 계산
    loss = criterion(y_pred, y)

    # 5.3 역전파 및 가중치 업데이트
    optimizer.zero_grad()  # 이전 기울기 초기화
    loss.backward()         # 기울기 계산
    optimizer.step()        # 가중치 업데이트

    # 5.4 에폭마다 손실 출력 (선택 사항)
    if (epoch + 1) % 10 <mark class="highlight"> 0:
        print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')

위 예제는 nn 모듈을 사용하여 MLP 모델을 정의하고, Autograd를 통해 모델을 학습합니다.

  1. 데이터 생성: 입력 데이터 X와 정답 y를 텐서로 생성합니다.
  2. 모델 정의: nn.Module을 상속받아 MLP 모델을 정의합니다. nn.Linear 레이어와 활성화 함수(ReLU)를 사용하여 모델의 구조를 설계합니다.
  3. 모델 인스턴스 생성: 정의된 모델 클래스를 사용하여 모델 인스턴스를 생성합니다.
  4. 손실 함수와 옵티마이저 정의: nn.MSELoss를 사용하여 손실 함수를 정의하고, torch.optim.SGD를 사용하여 옵티마이저를 정의합니다. 옵티마이저는 모델의 파라미터(가중치)를 학습률(learning rate)을 사용하여 업데이트합니다.
  5. 학습 루프: 각 에폭마다 순전파, 손실 계산, 역전파, 가중치 업데이트 과정을 반복합니다.
    • 순전파: 모델에 입력을 전달하여 예측값을 계산합니다.
    • 손실 계산: 예측값과 정답 간의 손실을 계산합니다.
    • 역전파 및 가중치 업데이트: optimizer.zero_grad()를 사용하여 이전 기울기를 초기화하고, loss.backward()를 호출하여 기울기를 계산한 후, optimizer.step()을 사용하여 가중치를 업데이트합니다.

4. 주의사항과 트러블슈팅

1) 기울기 초기화

각 학습 루프(epoch)마다 기울기를 초기화하는 것이 중요합니다. 기울기를 초기화하지 않으면, 이전 기울기가 누적되어 정확한 기울기 계산을 방해하고 학습이 제대로 진행되지 않을 수 있습니다.== PyTorch에서는 optimizer.zero_grad() 또는 model.zero_grad()를 사용하여 기울기를 초기화합니다.

2) requires_grad 설정 확인

모델을 정의하거나 데이터를 전처리할 때, requires_grad 설정을 올바르게 확인해야 합니다. 모델의 학습 가능한 파라미터(가중치)는 requires_grad=True로 설정되어 있어야 하며, 입력 데이터 역시 기울기 계산이 필요한 경우 requires_grad=True로 설정되어야 합니다. 그렇지 않으면 기울기가 계산되지 않아 모델이 학습되지 않습니다.

3) 계산 그래프 (Computational Graph) 이해

Autograd는 계산 그래프를 기반으로 작동합니다. 계산 그래프는 연산의 순서를 나타내는 방향성 비순환 그래프(Directed Acyclic Graph, DAG)입니다. Autograd는 이 그래프를 따라가면서 기울기를 계산합니다.

계산 그래프 설명 뒤

위 그림은 간단한 계산 그래프의 예시를 보여줍니다. 각 노드는 텐서 또는 연산을 나타내며, 엣지는 데이터의 흐름을 나타냅니다. Autograd는 계산 그래프를 추적하여 기울기를 계산하므로, 계산 그래프의 구조를 이해하는 것이 중요합니다. 특히, detach(), no_grad()와 같은 함수를 사용할 때, 계산 그래프의 변화를 인지하는 것이 중요합니다.

4) 메모리 문제

Autograd는 기울기 계산을 위해 중간 값을 저장하기 때문에, 복잡한 모델이나 긴 시퀀스를 처리하는 경우 메모리 사용량이 증가할 수 있습니다. 메모리 문제를 해결하기 위해 다음과 같은 방법을 고려할 수 있습니다.

  • 배치 크기(batch size) 조절: 배치 크기를 줄이면 각 단계에서 처리되는 데이터의 양이 줄어들어 메모리 사용량을 줄일 수 있습니다.
  • 그래디언트 클리핑(gradient clipping): 기울기 값이 너무 커지는 것을 방지하여 메모리 사용량을 줄이고 학습 안정성을 향상시킬 수 있습니다.
  • detach() 사용: 불필요한 텐서를 계산 그래프에서 분리하여 메모리 사용량을 줄일 수 있습니다.
  • torch.cuda.empty_cache() 사용: GPU 메모리를 비워줍니다.
  • 모델 병렬화: 모델을 여러 GPU에 분산하여 메모리 사용량을 분산시킬 수 있습니다.

5. 결론

자동 미분(Autograd)은 딥러닝 프레임워크의 핵심 기능으로, 신경망 학습을 위한 기울기 계산을 자동화합니다. PyTorch의 Autograd는 requires_grad, grad, backward() 함수를 통해 제공되며, 역전파 알고리즘을 기반으로 동작합니다. Autograd를 효과적으로 사용하기 위해서는 계산 그래프, 기울기 초기화, requires_grad 설정 등을 이해하는 것이 중요합니다. 딥러닝 모델을 설계하고 학습하는 과정에서 Autograd는 핵심적인 역할을 수행하며, 딥러닝 개발의 효율성을 크게 향상시킵니다.

비슷한 글 추천

Comments (0)

No comments yet. Be the first to comment!