내용
자동미분(Autograd)
torch.autograd
신경망은 설정한 층들을 통과하면서 feature와 label의 관계를 적합하게 설명하는 모델을 구축합니다. 이 과정을 학습(train)이라고 하며 이 학습은 다음과 같이 두 단계로 구성됩니다.
$$X \rightarrow \left(\begin{aligned}\color{blue}{W\cdot x+b \rightarrow \hat{y}(\text{forward})}\\\uparrow \qquad\qquad \qquad \downarrow\\\color{red}{\frac{dw}{dx}, \frac{db}{dx}(\text{backward})}\end{aligned} \right)\rightarrow \hat{Y}$$위 식에서 W, b는 각각 가중치와 편향입니다.
위의 역전파(backward) 단계서는 순전파(forward)에서 생성된 가중치와 편향에 의한 추정치 $\hat{y}$와 실제값인 label(Y)와의 오차(error)를 계산하고 그 값에 대한 가중치와 편향을 평가(변화도, 미분)하여 그 매개변수(W, b)를 조절합니다. 이 조절을 매개변수의 업데이트(update)라고 하며 그 차이가 최소 또한 변화가 없을 수준까지 이 과정은 반복합니다. 이 과정은 최적화(optimization)라고 하며 그 결과가 최종의 모델이 됩니다.
pytorch에서 torch.autograd
는 역전파시 실행되는 미분과정을 담당하는 자동미분엔진입니다.
autograd는 데이터(텐서)의 실행된 모든 연산들(연산 결과가 새로운 텐서인 경우도 포함하여)의 기록을 Function 객체로 구성된 방향성 비순환 그래프(DAG; Directed Acyclic Graph)에 저장(keep)합니다.
그림 1과 같이 방향성 비순환 그래프(DAG)의 잎(leave)은 입력 텐서(feature, W0, b0)이고, 뿌리(root)는 결과 텐서(추정치, 최종 w, b)입니다. 이 그래프를 뿌리에서부터 잎까지 추적하면 연쇄 법칙(chain rule)에 따라 변화도를 자동으로 계산할 수 있습니다.
순전파 단계에서, autograd는 다음 두 가지 작업을 동시에 수행합니다.
- 요청된 연산을 수행하여 결과 텐서를 계산
- 역전파 단계에서 계산된 변화도를 유지
역전파는 순전파 단계에서 계산된 loss에 .backward()
가 호출될 때 시작됩니다. autograd
에 의해 계산하고 기록합니다.
- 각 변수에 적용한 연산의 종류는
.grad_fn
로 확인 - 각 텐서의
.grad
속성에 계산 결과를 누적(accumulate) - 연쇄 법칙을 사용하여, 모든 잎(leaf) 텐서들까지 전파(propagate)
requires_grad 설정
requires_grad=True로 설정된 변수에만 변화도가 계산되고 기록됩니다. 즉 역전파(.backward()) 동안 require_grad=True로 설정된 leaf tensor에만 .grad 필드가 누적됩니다. 그러므로 require_grad=True가 설정된 텐서(grad_fn이 존재하는 텐서)에 대해 계산됩니다. 그러므로 순전파에서 주어지거나 계산되는 nn.Module
의 매개변수 등은 그래디언트(미분)의 대상이 되며 grad_fn이 존재합니다. pytorch의 nn.Module에서 자동으로 설정되는 W, b의 초기값에는 자동으로 requires_grad=True가 설정됩니다. 또한 nn.Module.requires_grad_()
를 사용하여 모듈 수준에서 requires_grad를 설정할 수도 있습니다. 모듈에 적용하면 .requires_grad_()는 모든 모듈의 매개변수(기본적으로 require_grad=True가 있음)에 적용됩니다.
모델의 일부를 고정하기위해 업데이트하지 않으려는 매개변수에 .requires_grad_(False)를 적용하기만 하면 됩니다. 이 경우 .grad 필드가 업데이트되지 않습니다.
다음 코드에서 입력 x, 매개변수 w 와 b , 그리고 일부 손실 함수가 있는 가장 간단한 단일 계층 신경망을 가정하겠습니다. PyTorch에서는 다음과 같이 정의할 수 있습니다
import torch, torchvision torch.manual_seed(1) x = torch.ones(5) # input tensor y = torch.zeros(3) # expected output w = torch.randn(5, 3, requires_grad=True) b = torch.randn(3, requires_grad=True) z = torch.matmul(x, w)+b loss = torch.nn.functional.mse_loss(z, y) x, w
(tensor([1., 1., 1., 1., 1.]), tensor([[ 0.6614, 0.2669, 0.0617], [ 0.6213, -0.4519, -0.1661], [-1.5228, 0.3817, -1.0276], [-0.5631, -0.8923, -0.0583], [-0.1955, -0.9656, 0.4224]], requires_grad=True))
z
tensor([-0.7313, -2.0824, -1.2786], grad_fn=&tl;AddBackward0>)
loss
tensor(2.1687, grad_fn=&tl;MseLossBackward>)
이 신경망에서, w와 b는 최적화해야 하는 매개변수입니다. 따라서
이러한 변수들에 대한 손실 함수의 변화도를 계산할 수 있어야 합니다. 이를 위해서 해당 텐서에 requires_grad
속성을 설정합니다.
requires_grad
의 값은 텐서를 생성할 때 설정하거나, 나중에
x.requires_grad_(True)
메소드를 사용하여 나중에 설정할 수도 있습니다.
역방향 전파 함수에 대한 참조(reference)는 텐서의 grad_fn
속성에 저장됩니다. 이 속성은 연산그래프를 구성하기 위해 텐서에 적용하는 Function의 클래스 객체입니다.
print('Gradient function for z =', z.grad_fn) print('Gradient function for loss =', loss.grad_fn)
Gradient function for z = <AddBackward0 object at 0x7f4d931503a0> Gradient function for loss = <MseLossBackward object at 0x7f4dd41fe1f0>
변화도(Gradient) 계산하기
신경망에서 매개변수의 가중치를 최적화하려면 매개변수에 대한 손실함수의 도함수(derivative)를 계산해야 합니다. 즉 x에 대한 loss의 변화량으로 $\frac{\partial w}{\partial x}$와 $\frac{\partial b}{\partial x}$를 계산합니다.
이러한 도함수를 계산하기 위해, loss.backward()
를 호출한 다음 w.grad
와 b.grad
에서 값을 가져옵니다.
loss.backward() print(w.grad) print(b.grad)
tensor([[-1.4688, 0.9328, -0.3732], [-1.4688, 0.9328, -0.3732], [-1.4688, 0.9328, -0.3732], [-1.4688, 0.9328, -0.3732], [-1.4688, 0.9328, -0.3732]]) tensor([-1.4688, 0.9328, -0.3732])
- 성능 상의 이유로, 주어진 그래프에서의
backward
를 사용한 변화도 계산은 한 번만 수행할 수 있습니다. 즉, 한 번의 미분만 가능하다. 미분의 결과를 다시 미분하기 위해서는backward(etain_graph=True)
와 같이 이 함수에 retain_graph=True을 전달해야 합니다. 이러한 지정은 연속된 미분이 필요시 모든 경우에서 지정해야 합니다.
loss.backward()
--------------------------------------------------------------------------- RuntimeError: Trying to backward through the graph a second time (or directly access saved variables after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved variables after calling backward.
loss1 = torch.nn.functional.mse_loss(z, y) loss1.backward(retain_graph=True) w.grad #첫번째 미분
tensor([[-0.4876, -1.3883, -0.8524], [-0.4876, -1.3883, -0.8524], [-0.4876, -1.3883, -0.8524], [-0.4876, -1.3883, -0.8524], [-0.4876, -1.3883, -0.8524]])
loss1.backward(retain_graph=True) #두번째미분 w.grad
tensor([[-0.9751, -2.7766, -1.7048], [-0.9751, -2.7766, -1.7048], [-0.9751, -2.7766, -1.7048], [-0.9751, -2.7766, -1.7048], [-0.9751, -2.7766, -1.7048]])
loss1.backward() #3번째 미분 w.grad
tensor([[-1.4627, -4.1648, -2.5572], [-1.4627, -4.1648, -2.5572], [-1.4627, -4.1648, -2.5572], [-1.4627, -4.1648, -2.5572], [-1.4627, -4.1648, -2.5572]])
변화도 추적 멈추기
기본적으로 requires_grad=True
를 설정한 텐서는 변화도의 계산을 지원하고 기록이 추적됩니다. 그러나 학습단계에서 구축한 모델을 검정하는 단계에서는 순전파 단계만 필요합니다. 이와같이 추적이나 지원이 필요없을 경우에는 torch.no_grad()
블록으로 이 추적을 멈출수 있습니다.
신경망의 일부 매개변수를 고정해서 사용할 경우 즉, 학습된 매개변수의 미세한 조정이 필요한 경우 변화도 추적을 멈추어야 합니다. 이러한 과정은 연산속도가 향상됩니다.
z = torch.matmul(x, w)+b print(z.requires_grad) with torch.no_grad(): z = torch.matmul(x, w)+b print(z.requires_grad)
True False
동일한 결과를 얻는 다른 방법은 텐서에 detach()
메소드를 사용하는 것입니다.
z = torch.matmul(x, w)+b z_det = z.detach() print(z_det.requires_grad)
False
z_det
tensor([-0.7313, -2.0824, -1.2786])
Grad Modes
require_grad 설정 외에도 PyTorch의 계산이 autograd에 의해 내부적으로 처리되는 방식에 영향을 줄 수 있는 즉 그래디언트의 활성 또는 비활성을 위한 세 가지 모드가 있습니다.
- 기본 모드(grad 모드)
- no_grad나 평가모드가 선언되지 않은 경우에 적용되는 암시적 모드입니다. 즉, no_grad나 평가모드를 진행하기 위해서는 그 모드가 선언되어야 것에 반해 이 모드는 단지 이 두 모드가 선언되지 않은 상태에서 진행됩니다.
- 기본모드는 required_grad=True가 적용되지만 다른 두 모드에서는 require_grad=False로 재정의됩니다.
- no_grad 모드
- 이 모드하에서는 변화량이 계산되지 않습니다. 즉, 역전파 단계는 이루어지지 않습니다.
- 텐서에 requires_grad=False로 설정함으로서그래디언트를 비활성화하는 것과 같습니다.
- Torch.nn.init의 구현은 초기화된 매개변수를 제자리에서 업데이트할 때 autograd 추적을 피하기 위해 매개변수를 초기화할 때 no_grad 모드에 의존합니다.
- 추론 모드
- 평가 모드(nn.Module.eval())
- eval()을 사용하여 module로 하여금 inference mode로 바꾸어주게 되면, (gradient를 계산하지 않도록 함으로써) inference 속도 뿐만 아니라, dropout 또는 batch-normalization과 같은 training과 inference 시에 다른 forward() 동작을 하는 module들에 대해서 각기 때에 따라 올바른 동작을 하도록 합니다. 다만, inference가 끝나면 다시 train()을 선언 해 주어, 원래의 훈련모드로 돌아가게 해 주어야 합니다.
- 구축되는 모델에 따라 중지되는 부분이 차이가 보임
학습 model.eval()#평가모드 선언 y_hat = model(x_valid) model.train()#학습단계로 확인
댓글
댓글 쓰기