티스토리 뷰
torch Autograd Examples
torch의 주요 기능 자동 미분 !!! 을 R에서 연습해보자
1. Simple Minimization
\(f(x_1, x_2) = 0.2x_1^2 + 0.2x_2^2 - 5\) 은 \((0, 0)\) 에서 최소가 된다. 최적화하고자 하는 변수는 \(x_1\) , \(x_2\) . 여기서는 source tensors라고 한다.
# source tensors
x1 <- torch_tensor(2, requires_grad = TRUE)
x2 <- torch_tensor(2, requires_grad = TRUE)
Autograd가 작동하는 중간 과정을 보기 위해 \(f(x_1, x_2)\) 를 분해해서 변수로 만든다. 이때 구성한 \(x_3,\cdots, x_6\) 은 invisible nodes라고 하겠다.
# invisible nodes
x3 <- x1$square()
x5 <- x3 * 0.2
x4 <- x2$square()
x6 <- x4 * 0.2
x7 <- x5 + x6 - 5
x7
## torch_tensor
## -3.4000
## [ CPUFloatType{1} ][ grad_fn = <SubBackward1> ]
source tensors를 만들 때 requires_grad = TRUE
옵션을 걸어주었기 때문에, 그래프의 모든 의존(dependent) 노드들은 이 속성을 이어 받는다.
x7$requires_grad
## [1] TRUE
결과값인 \(x_7\) 이 어떻게 \(x_1\) 과 \(x_2\) 에 어떻게 의존하는지 결정하기 위해 backward()
를 호출한다.
x7$backward()
호출로 인해 $grad
fields가 \(x_1\) 과 \(x_2\) 에 채워졌다. \(\frac{\partial{f}}{\partial{x_1}}=0.4x_1\) 이고 \(x_1=x_2=2\) 이므로 \(x_1\) 의 gradient는 0.8이다. ( \(x_2\) 도 마찬가지)
x1$grad
## torch_tensor
## 0.8000
## [ CPUFloatType{1} ]
x2$grad
## torch_tensor
## 0.8000
## [ CPUFloatType{1} ]
중간 과정인 \(x_3\) 등의 gradient를 볼 수 있을까? 아래 코드를 실행해보면 Undefined
임을 알 수 있다. retain_grad = TRUE
를 하지 않았기 때문에 field가 채워지지 않은 것!
x3$grad
## torch_tensor
## [ Tensor (undefined) ]
invisible node들에 대해서 $retain_grad()
x3 <- x1$square()
x3$retain_grad()
x5 <- x3 * 0.2
x5$retain_grad()
x4 <- x2$square()
x4$retain_grad()
x6 <- x4 * 0.2
x6$retain_grad()
x7 <- x5 + x6 - 5
x7$backward()
이제 \(x_3\) 의 gradient field가 채워졌다.
x3$grad
## torch_tensor
## 0.2000
## [ CPUFloatType{1} ]
x4$grad
## torch_tensor
## 0.2000
## [ CPUFloatType{1} ]
x5$grad
## torch_tensor
## 1
## [ CPUFloatType{1} ]
x6$grad
## torch_tensor
## 1
## [ CPUFloatType{1} ]
중간 과정인 invisible 노드들과 \(x_1\) , \(x_2\) 들의 연결 고리들은 tensor의 $grad_fn
field에 저장된다.
x3$grad_fn
## PowBackward0
x4$grad_fn
## PowBackward0
x5$grad_fn
## MulBackward1
x6$grad_fn
## MulBackward1
2. Rosenbrock function
- Function minimization with Autograd
Rosenbrock 함수는 \((1,1)\) 에서 최소가 된다.
a <- 1; b <- 5
rosenbrock <- function(x){
x1 <- x[1]
x2 <- x[2]
(a - x1)^2 + b * (x2 - x1 ^2)^2
}
Minimization from scratch
\(x\) 가 최적화해야 하는 변수이다. 다시 말해, \(x\) 에 대해 함수를 미분해야 한다. 그러므로 requires_grad = TRUE
옵션을 걸어준다.
x <- torch_tensor(c(-1, 1), requires_grad = TRUE)
value <- rosenbrock(x)
value$backward()
backward()
를 언제 호출했든 torch는 tensor에 수행된 모든 연산을 기록하고 모든 derivative들을 계산한다. 하지만, \(x\) 를 update하기 위한 subtraction에서는 derivative 계산을 원하지 않는다! torch가 해당 연산을 기록하지 않게 하기 위해 with_no_grad()
로 감싸준다.
또한, torch는 디폴트로 gradient들을 grad
field에 저장한다. 그러므로 grad$zero_()
로 모든 새로운 계산에 대해 gradient들을 0으로 만들어 준다.
num_iterations <- 1000
lr <- 0.01
with_no_grad({
x$sub_(lr* x$grad)
x$grad$zero_()
})
## torch_tensor
## 0
## 0
## [ CPUFloatType{2} ]
Final code
num_iterations <- 1000
lr <- 0.01
x <- torch_tensor(c(-1, 1), requires_grad = TRUE)
for (i in 1:num_iterations) {
if (i %% 100 == 0) cat("Iteration: ", i, "\n")
value <- rosenbrock(x)
if (i %% 100 == 0) {
cat("Value is: ", as.numeric(value), "\n")
}
value$backward()
if (i %% 100 == 0) {
cat("Gradient is: ", as.matrix(x$grad), "\n")
}
with_no_grad({
x$sub_(lr * x$grad)
x$grad$zero_()
})
}
## Iteration: 100
## Value is: 0.3502924
## Gradient is: -0.667685 -0.5771312
## Iteration: 200
## Value is: 0.07398106
## Gradient is: -0.1603189 -0.2532476
## Iteration: 300
## Value is: 0.02483024
## Gradient is: -0.07679074 -0.1373911
## Iteration: 400
## Value is: 0.009619333
## Gradient is: -0.04347242 -0.08254051
## Iteration: 500
## Value is: 0.003990697
## Gradient is: -0.02652063 -0.05206227
## Iteration: 600
## Value is: 0.001719962
## Gradient is: -0.01683905 -0.03373682
## Iteration: 700
## Value is: 0.0007584976
## Gradient is: -0.01095017 -0.02221584
## Iteration: 800
## Value is: 0.0003393509
## Gradient is: -0.007221781 -0.01477957
## Iteration: 900
## Value is: 0.0001532408
## Gradient is: -0.004811743 -0.009894371
## Iteration: 1000
## Value is: 6.962555e-05
## Gradient is: -0.003222887 -0.006653666
x
## torch_tensor
## 0.9918
## 0.9830
## [ CPUFloatType{2} ][ requires_grad = TRUE ]
\(x\) 가 실제 최소값인 \((1, 1)\) 에 가깝게 나왔다.