티스토리 뷰

Data Analysis/R

torch Autograd in R

yeorii 2024. 4. 21. 20:01

 

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)\) 에 가깝게 나왔다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함