이번 포스팅에서는 Pytorch를 사용해 신경망을 구축하는 과정에서 어떤 함수가 사용되는지 정리해보았다. 먼저, 신경망(Neural Network)의 간단한 학습 과정을 알아보고, 함수를 알아보자.
신경망의 정의 및 목표
신경망 학습은 모델 정의, 손실 함수 선택, 최적화 알고리즘 선택 및 학습 단계로 구분된다. 해당 학습 과정은 기본적으로 손실 함수의 값을 최소화하는 매개변수(가중치와 편향)을 찾는 것을 목표로 한다.
- 모델 정의 : 신경망은 여러 계층(layers)로 구성되며, 각 계층은 입력을 받아 출력을 생성한다. 계층에서 수행되는 연산은 매개변수(Weight and Bias)로 구성된다. pytorch에서는 매개변수가 Tensor 형태로 저장된다.
- 손실 함수 선택 : 손실 함수(loss function) 혹은 목적 함수(objective function)는 예측 값과 실제 값 사이의 사이를 산출한다. 일반적으로 회귀 문제에서는 평균 제곱 오차(Mean Squared Error), 분류 문제에서는 교차 엔트로피(Cross Entropy) 등이 자주 사용된다.
- 최적화 알고리즘 선택(Optimizer) : 최적화 알고리즘은 손실함수 값을 최소화하기 위해 매개변수 값을 어떻게 조정할지 결정한다. 대표적인 방법으로 경사하강법(Gradient Descent), 확률적 경사하강법(Stochastic Gradient Descent), Adam 등이 있다. 객체 초기화 시 최적화 해야 할 파라미터(Tensor)들과 learning rate 등 하이퍼파라미터를 전달받아 관리하며, step() 메소드를 통해 파라미터 업데이트를 수행한다.
- 경사하강법(Gradient Descent) : Gradient Descent는 최적화 알고리즘의 일종으로, loss function을 최소화하기 위해 반복적으로 모델의 매개변수를 조정하는 방법이다. loss function의 Gradient(기울기)를 계산하고, 그 기울기가 가리키는 가장 가파른 하강 방향으로 매개변수를 업데이트한다. 이 과정을 여러 번 반복하면서, 점차 loss function의 값을 줄여 나간다. 즉, Gradient Descent는 신경망 학습에 필수적인 알고리즘이며, 최적화 알고리즘의 기본 구성 요소로 사용된다. 정말 중요한 개념이라서, 다른 개념보다 조금 자세히 서술해보았다. 기본 단계는 다음과 같다.
- 초기 매개변수 값(랜덤 또는 다른 방법으로 선택)을 설정
- 손실 함수에 대한 그래디언트를 계산
- 그래디언트가 가리키는 반대 방향으로 매개변수를 업데이트: new_param = old_param - learning_rate * gradient
- 수렴할 때까지 2-3 단계를 반복
- 여기서 학습률(learning rate)은 매개변수 업데이트 크기를 결정하는 하이퍼파라미터이다. 학습률이 너무 크면 수렴하지 않을 수 있고, 너무 작으면 학습이 느려질 수 있다.
- Gradient Descent의 변형은 다음과 같다.
- 배치 경사 하강법 : 전체 데이터에 대한 loss function의 gradient를 계산하여 한 번에 매개 변수를 업데이트
- 확률적 경사 하강법 : 각 훈련 sample에 대해 개별적으로 Gradient를 계산하고, 즉시 매개변수를 업데이트
- 미니 배치 경사 하강법 : 데이터의 작은 랜덤 샘플(mini-batch)에 대한 Gradient를 계산하여 매개변수를 업데이트
학습 단계
- 순전파(Forward propagation) : 처음에는 입력 데이터가 네트워크를 통과하면서 개별 노드의 출력값이 계산된다. 각 노드는 입력값과 가중치를 곱한 후 모두 더하고, 그 결과에 활성화 함수를 적용하여 출력값을 생성한다. 이렇게 계산된 출력값은 다음 노드로 전달되며, 이 과정이 반복되어 최종적으로 네트워크의 출력값을 얻는다.
- 손실 함수 : 네트워크의 최종 출력(예측 값)과 실제 Target 변수의 값 차이를 손실 함수를 통해 계산한다.
- 역전파(Backward Propagation) : 순전파 단계에서 저장해둔 개별 노드의 출력값을 사용하여, 손실 함수에 대한 가중치와 편향의 Gradient(미분값)을 네트워크의 역방향으로 전달한다. 연쇄 법칙(Chain rule)을 활용하여 현재 Layer에서 발생한 error에 다음 Layer에서 전달된 Gradient를 곱하여 해당 Layer에 대한 Gradient를 구한다. 이는가중치와 편향이 변할 때 손실 함수가 얼마나 변하는지를 나타내며, Gradient Descent 등을 활용하여 가중치와 편향을 수정하는 근거가 된다. Gradient가 음수라면 해당 가중치나 편향을 증가시키면 손실 함수의 값이 줄어든다는 것을 의미하고, Gradient가 양수라면 해당 가중치나 편향을 감소시켜야 손실 함수의 값이 줄어든다는 것을 의미한다.
- 검증과 평가 : 일정 Epoch(전체 훈련 데이터에 대한 한 번의 반복 과정) 혹은 학습이 끝난 후, 검증 데이터를 통해 모델의 성능을 검증한다. 이를 통해 과적합(Overfitting) 등의 문제가 있는지 확인하고, 필요에 따라 학습 과정을 조정한다. 최종적으로, 테스트 데이터로 모델의 일반화 성능을 평가한다.
지금까지 신경망의 학습 과정을 간단하게 정리했다. 다음으로, 신경망을 구축할 때 Pytorch에서 제공하는 다양한 기능들에 대해 살펴보자.
Autograd
Pytorch의 Autograd는 코드를 실행하는 방식으로 동적 계산 그래프를 생성한다. 각 노드는 Tensor를, 에지는 생성 함수를 나타낸다. 해당 그래프를 통해 역전파가 수행되며, Gradient가 자동으로 계산된다. 주요 구성 요소나 메서드들은 다음과 같다.
- Tensor : Pytorch에서 데이터를 표현하는 기본 단위이다. Numpy의 ndarray와 유사하게 다차원 배열을 표현하지만, GPU 상에서 실행 가능하며 Autograd 연산에 최적화 되어있다.
- require_grad : Tensor 객체에 대해 이 속성이 True로 설정되면, 해당 Tensor에서 이루어지는 모든 연산들이 계산 그래프에 포함된다. 따라서 역전파가 진행될 때 Gradient가 계산되고 .grad 속성에 저장된다.
- grad : require_grad 속성이 True인 Tensor의 속성은 해당 Tensor에 대한 Gradient 정보를 저장한다.
- backward() : Autograd 연산을 사용하여 Gradient를 자동으로 계산한다. 만약 Tensor가 스칼라(Scalar)일 경우 backward() 인자 없이 호출할 수 있다. 하지만, 여러 요소를 가진 경우(vector, array 등) 해당 Tensor와 동일한 형태(shape)의 인자가 필요하다.
- grad_fn : 개별 Tensor는 자신을 생성한 Function과 연결되어 있으며, 이 정보가 grad_fn에 저장된다. 단, 사용자가 직접 생성한 Tensor는 제외된다.
아래 코드는 pytorch에서 vector에 대해 backward() 함수를 호출하는 예시이다.
import torch
# 크기가 3인 벡터 생성
v = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
# v에 연산을 수행하여 결과 y 생성
y = v * 2
# y.backward() 호출 시 오류 발생 (스칼라가 아니므로)
# y.backward()
# 동일한 크기의 tensor [1., 1., 1.]을 인자로 전달
gradient_vector = torch.tensor([1., 1., 1.])
y.backward(gradient_vector)
print(v.grad) # tensor([2., 2., 2.]) 출력 (각 요소에 대한 그래디언트 계산됨)
Zero_grad()
Pytorch에서는 각각의 연산에 대한 Gradient 정보를 누적하여 저장한다. 따라서 한 번의 역전파가 끝난 후에는 zero_grad() 함수를 호출하여 Gradient를 0으로 초기화해야한다. 만약 zero_grad()가 호출되지 않으면, Gradient 정보가 계속 누적되어 이전 step에서 계산된 Gradient에 현재 step에서 계산된 Gradient가 추가된다. 이로 인해 잘못된 가중치 업데이트가 발생할 수 있으며, 모델의 학습에 부정적인 영향을 미칠 수 있다. 따라서 일반적으로 매 학습 단계(epoch) 시작 전이나 개별 batch의 역전파 직후에 optimizer.zero_grad()를 호출하여 그래디언트를 0으로 초기화한다. 아래에 간단한 예시를 만들어보았다.
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
for inputs, labels in dataloader:
optimizer.zero_grad() # Gradient 초기화
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step() # 가중치 업데이트
With torch.no_grad()
torch.no_grad()는 Pytorch의 Autograd를 비활성화하는 context manager이다. 이를 사용하면 Gradient 계산이 필요하지 않은 경우에 메모리 사용량을 줄이고 연산 속도를 높일 수 있다. 예를 들어, 모델을 평가하는 경우에는 Gradient 정보가 필요하지 않기 때문에 torch.no_grad() 내에서 평가 코드를 작성한다. 예제는 다음과 같다.
model.eval()
with torch.no_grad():
for inputs, labels in dataloader:
outputs = model(inputs)
model.eval()은 모델을 평가 모드로 설정하는 함수이다. 이 함수를 호출하면 Dropout이나 Batch normalization과 같은 특정 층들이 학습 모드에서와 다르게 동작한다. torch.no_grad()와 model.eval()은 서로 다른 목적으로 사용되지만, 둘 다 모델 평가에 주로 사용된다.
- torch.no_grad(): 그래디언트 계산 비활성화 (메모리 절약 및 연산 속도 향상)
- model.eval(): 특정 층(Dropout, BatchNorm 등)의 동작을 학습 모드에서 평가 모드로 변경
두 함수는 종종 함께 사용되며, 이렇게 하면 평가과정에서 메모리 절약을 통해 빠른 결과를 확인할 수 있다.
'Data Science' 카테고리의 다른 글
| [NLP] LLM Benchmark (0) | 2023.10.03 |
|---|---|
| [Pytorch] CUDA 설치 (0) | 2023.09.22 |
| [NLP] Tokenizer (0) | 2023.09.20 |
| [KNIME] python 설정 (0) | 2023.09.18 |
| [KNIME] 설치방법 (0) | 2023.09.16 |