기본 콘텐츠로 건너뛰기

[ML] 결정트리(Decision Tree) 모델

자동미분(Autograd)

내용

자동미분(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)에 따라 변화도를 자동으로 계산할 수 있습니다.

그림 1. 단순 신경망의 흐름과 노드와 잎.

순전파 단계에서, 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, 매개변수 wb , 그리고 일부 손실 함수가 있는 가장 간단한 단일 계층 신경망을 가정하겠습니다. 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>)

이 신경망에서, wb는 최적화해야 하는 매개변수입니다. 따라서 이러한 변수들에 대한 손실 함수의 변화도를 계산할 수 있어야 합니다. 이를 위해서 해당 텐서에 requires_grad 속성을 설정합니다.

Note

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.gradb.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])
Note
  • 성능 상의 이유로, 주어진 그래프에서의 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()#학습단계로 확인

댓글

이 블로그의 인기 게시물

[Linear Algebra] 유사변환(Similarity transformation)

유사변환(Similarity transformation) n×n 차원의 정방 행렬 A, B 그리고 가역 행렬 P 사이에 식 1의 관계가 성립하면 행렬 A와 B는 유사행렬(similarity matrix)이 되며 행렬 A를 가역행렬 P와 B로 분해하는 것을 유사 변환(similarity transformation) 이라고 합니다. $$\tag{1} A = PBP^{-1} \Leftrightarrow P^{-1}AP = B $$ 식 2는 식 1의 양변에 B의 고유값을 고려한 것입니다. \begin{align}\tag{식 2} B - \lambda I &= P^{-1}AP – \lambda P^{-1}P\\ &= P^{-1}(AP – \lambda P)\\ &= P^{-1}(A - \lambda I)P \end{align} 식 2의 행렬식은 식 3과 같이 정리됩니다. \begin{align} &\begin{aligned}\textsf{det}(B - \lambda I ) & = \textsf{det}(P^{-1}(AP – \lambda P))\\ &= \textsf{det}(P^{-1}) \textsf{det}((A – \lambda I)) \textsf{det}(P)\\ &= \textsf{det}(P^{-1}) \textsf{det}(P) \textsf{det}((A – \lambda I))\\ &= \textsf{det}(A – \lambda I)\end{aligned}\\ &\begin{aligned}\because \; \textsf{det}(P^{-1}) \textsf{det}(P) &= \textsf{det}(P^{-1}P)\\ &= \textsf{det}(I)\end{aligned}\end{align} 유사행렬의 특성 유사행렬인 두 정방행렬 A와 B는 'A ~ B' 와 같

[matplotlib] 히스토그램(Histogram)

히스토그램(Histogram) 히스토그램은 확률분포의 그래픽적인 표현이며 막대그래프의 종류입니다. 이 그래프가 확률분포와 관계가 있으므로 통계적 요소를 나타내기 위해 많이 사용됩니다. plt.hist(X, bins=10)함수를 사용합니다. x=np.random.randn(1000) plt.hist(x, 10) plt.show() 위 그래프의 y축은 각 구간에 해당하는 갯수이다. 빈도수 대신 확률밀도를 나타내기 위해서는 위 함수의 매개변수 normed=True로 조정하여 나타낼 수 있다. 또한 매개변수 bins의 인수를 숫자로 전달할 수 있지만 리스트 객체로 지정할 수 있다. 막대그래프의 경우와 마찬가지로 각 막대의 폭은 매개변수 width에 의해 조정된다. y=np.linspace(min(x)-1, max(x)+1, 10) y array([-4.48810153, -3.54351935, -2.59893717, -1.65435499, -0.70977282, 0.23480936, 1.17939154, 2.12397372, 3.0685559 , 4.01313807]) plt.hist(x, y, normed=True) plt.show()

R 미분과 적분

내용 expression 미분 2차 미분 mosaic를 사용한 미분 적분 미분과 적분 R에서의 미분과 적분 함수는 expression()함수에 의해 생성된 표현식을 대상으로 합니다. expression expression(문자, 또는 식) 이 표현식의 평가는 eval() 함수에 의해 실행됩니다. > ex1<-expression(1+0:9) > ex1 expression(1 + 0:9) > eval(ex1) [1] 1 2 3 4 5 6 7 8 9 10 > ex2<-expression(u, 2, u+0:9) > ex2 expression(u, 2, u + 0:9) > ex2[1] expression(u) > ex2[2] expression(2) > ex2[3] expression(u + 0:9) > u<-0.9 > eval(ex2[3]) [1] 0.9 1.9 2.9 3.9 4.9 5.9 6.9 7.9 8.9 9.9 미분 D(표현식, 미분 변수) 함수로 미분을 실행합니다. 이 함수의 표현식은 expression() 함수로 생성된 객체이며 미분 변수는 다음 식의 분모의 변수를 의미합니다. $$\frac{d}{d \text{변수}}\text{표현식}$$ 이 함수는 어떤 함수의 미분의 결과를 표현식으로 반환합니다. > D(expression(2*x^3), "x") 2 * (3 * x^2) > eq<-expression(log(x)) > eq expression(log(x)) > D(eq, "x") 1/x > eq2<-expression(a/(1+b*exp(-d*x))); eq2 expression(a/(1 + b * exp(-d * x))) > D(eq2, "x") a * (b * (exp(-d * x) * d))/(1 + b