Optimisasi Statistika - Metode Gradien

Video Pembelajaran - P7

Video Pembelajaran dapat diakses melalui link berikut : https://ipb.link/materiopstat

Pengantar

Metode optimisasi bertujuan untuk menemukan nilai minimum (atau maksimum) dari suatu fungsi tujuan \(f(x)\). Fungsi \(f(x)\) bisa berupa fungsi non-linear, seperti fungsi Rosenbrock berikut: \[ f(x) = 100(x_2 - x_1^2)^2 + (1 - x_1)^2 \]

f<-function(x) {
  x1=x[1]; x2=x[2];
  100*(x2-x1^2)^2+(1-x1)^2
}

x0<-c(-0,0)

library(numDeriv)
library(pracma)

Fungsi ini memiliki bentuk non-linear dengan sebuah lembah sempit yang membuatnya menantang untuk dioptimalkan. Minimum globalnya adalah \(f(1, 1) = 0\), yang terjadi pada titik \((x_1, x_2) = (1, 1)\).


Metode Newton

Metode Newton menggunakan informasi gradien dan matriks Hessian untuk menemukan minimum fungsi. Formula iterasinya adalah: \[ x_{i+1} = x_i - \left( \nabla^2 f(x_i) \right)^{-1} \nabla f(x_i) \]

Komponen Utama:

  • Gradien: \(\nabla f(x) = \begin{bmatrix} \frac{\partial f}{\partial x_1} \\ \frac{\partial f}{\partial x_2} \end{bmatrix}\)

  • Hessian: \(\nabla^2 f(x) = \begin{bmatrix} \frac{\partial^2 f}{\partial x_1^2} & \frac{\partial^2 f}{\partial x_1 \partial x_2} \\ \frac{\partial^2 f}{\partial x_2 \partial x_1} & \frac{\partial^2 f}{\partial x_2^2} \end{bmatrix}\)

Keunggulan:

  1. Cepat konvergen jika \(x_0\) dekat dengan solusi.
  2. Cocok untuk fungsi kuadratik karena Hessian konstan.

Kelemahan:

  1. Membutuhkan komputasi invers Hessian (\(O(n^3)\)).
  2. Tidak stabil jika Hessian tidak positif definit.

Kode Implementasi di R

newton_optimize <- function(f, x0, tol = 1e-6, max_iter = 100) {
  iter <- 0
  while (iter < max_iter) {
    df <- grad(f, x0)  # Gradien
    ddf <- hessian(f, x0)  # Hessian
    dx <- solve(ddf, -df)  # Perbarui langkah
    x1 <- x0 + dx
    if (abs(f(x1) - f(x0)) < tol) return(list(x = x1, iter = iter))
    iter <- iter + 1
    x0 <- x1
  }
  return(NULL)
}
# Contoh penggunaan
x0 <- c(0, 0)
o1 <- newton_optimize(f, x0)

Metode Marquardt

Metode Marquardt adalah kombinasi dari metode Gradient Descent dan Newton. Metode ini menambahkan elemen damping (\(\lambda\)) ke Hessian untuk memperbaiki stabilitas iterasi.

Formula Iterasi:

\[ x_{i+1} = x_i - \left( \nabla^2 f(x_i) + \lambda I \right)^{-1} \nabla f(x_i) \] \(\lambda\) adalah parameter yang mengontrol panjang langkah. Jika iterasi menurun, \(\lambda\) dikurangi; jika tidak, \(\lambda\) diperbesar.

Keunggulan:

  1. Stabil untuk iterasi awal.
  2. Mampu menangani gradien kecil pada area tertentu.

Kelemahan:

  1. Pemilihan parameter damping \(\lambda\) sensitif.
  2. Tidak cocok untuk dimensi sangat tinggi.

Kode Implementasi di R

marquardt_optimize <- function(f, x0, tol = 1e-6, max_iter = 100, lambda = 1) {
  iter <- 0
  while (iter < max_iter) {
    df <- grad(f, x0)
    ddf <- hessian(f, x0)
    mu <- lambda * max(diag(ddf))  # Damping factor
    dx <- solve(ddf + mu * diag(length(x0)), -df)
    x1 <- x0 + dx
    if (abs(f(x1) - f(x0)) < tol) return(list(x = x1, iter = iter))
    iter <- iter + 1
    lambda <- ifelse(f(x1) < f(x0), lambda / 10, lambda * 10)
    x0 <- x1
  }
  return(NULL)
}
# Contoh penggunaan
o2 <- marquardt_optimize(f, x0)

Metode Quasi-Newton (DFP dan BFGS)

DFP (Davidon-Fletcher-Powell)

DFP memperbarui aproksimasi matriks Hessian (\(B_k\)) menggunakan: \[ B_{k+1} = B_k + \frac{s_k s_k^\top}{s_k^\top y_k} - \frac{B_k y_k y_k^\top B_k}{y_k^\top B_k y_k} \]

BFGS (Broyden-Fletcher-Goldfarb-Shanno)

Pengembangan lebih stabil dari DFP: \[ B_{k+1} = B_k + \frac{y_k y_k^\top}{y_k^\top s_k} - \frac{B_k s_k s_k^\top B_k}{s_k^\top B_k s_k} \]

Keunggulan:

  1. Tidak memerlukan Hessian eksplisit.
  2. Lebih stabil untuk fungsi non-linear.

Kode Implementasi

dfp_optimize <- function(f, x0, B0, tol = 1e-6, max_iter = 100) {
  # Initialize variables
  iter <- 0
  n <- length(x0)
  x <- x0
  B <- B0
  
  # Loop until convergence or maximum number of iterations is reached
  while (iter < max_iter) {
    # Calculate the gradient using numerical differentiation
    g <- grad(f, x)
    
    # Calculate the search direction
    d <- -B %*% g
    
    # Perform line search to find optimal step size
    alpha <- optimize(function(alpha) f(x + alpha*d), interval = c(0, 1))$minimum
    x_new <- x + alpha * d
    
    # Calculate the new gradient using numerical differentiation
    g_new <- grad(f, x_new)
    
    # Calculate the update to the Hessian matrix
    s <- alpha * d
    y <- g_new - g
    B <- B + ((s %*% t(s)) / as.numeric(t(s) %*% y)) - ((B %*% y %*% t(y) %*% B) / as.numeric(t(y) %*% B %*% y))
    
    # Check for convergence
    if (abs(f(x_new) - f(x)) < tol) {
      return(list(x=x_new, iter=iter))
    }
    
    # Update the iteration counter and the guess
    iter <- iter + 1
    x <- x_new
  }
  
  # If the algorithm does not converge within the maximum number of iterations, return NULL
  return(NULL)
}

# DFP
B0<-diag(2)
o3 <- dfp_optimize(f, x0, B0)

# BFGS dengan optim
o4 <- optim(par = x0, fn = f, method = "BFGS", control = list(trace = TRUE))
## initial  value 1.000000 
## iter  10 value 0.048025
## iter  20 value 0.000001
## final  value 0.000000 
## converged

Metode Gradient Descent

Formula Iterasi:

\[ x_{i+1} = x_i - \alpha \nabla f(x_i) \] di mana \(\alpha\) adalah learning rate.

Keunggulan:

  1. Implementasi sederhana.
  2. Cocok untuk fungsi dimensi tinggi.

Kelemahan:

  1. Pemilihan learning rate sulit.
  2. Lambat untuk fungsi dengan lembah sempit.

Kode Implementasi

gradient_descent <- function(obj_func, init_x, learning_rate = 0.01, maxIter = 1e5, tol = 1e-6) {
  x <- init_x
  gradx <- grad(obj_func, x)
  iter <- 0
  while (iter < maxIter && norm(as.matrix(gradx)) > tol) {
    gradx <- grad(obj_func, x)
    x <- x - learning_rate * gradx
    iter <- iter + 1
  }
  return(list(x = x, iter = iter))
}
# Contoh penggunaan
o5 <- gradient_descent(f, x0)

Perbandingan Metode

Berikut adalah hasil perbandingan dari semua metode:

# Newton
o1 <- newton_optimize(f, x0)

# Marquardt
o2 <- marquardt_optimize(f,x0)

# DFP
B0<-diag(2)
o3 <- dfp_optimize(f, x0, B0)

# BFGS
o4 <- optim(par=x0, fn=f, method="BFGS", control = list(trace=T))
## initial  value 1.000000 
## iter  10 value 0.048025
## iter  20 value 0.000001
## final  value 0.000000 
## converged
# Gradient Descent
o5 <- gradient_descent(obj_func=f, init_x=x0, learning_rate=0.001)

data.frame(Method = c("Newton", "Marquardt", "DFP", "BFGS", "Gradient Descent"), x1 = c(o1$x[1],o2$x[1],o3$x[1],o4$par[1],o5$x[1]), x2 = c(o1$x[2],o2$x[2],o3$x[2],o4$par[2],o5$x[2]), iter = c(o1$iter,o2$iter,o3$iter,o4$counts[2],o5$iter))
##             Method        x1        x2  iter
## 1           Newton 1.0000000 1.0000000     2
## 2        Marquardt 0.9999956 0.9999906    10
## 3              DFP 1.0000180 1.0000189    18
## 4             BFGS 0.9998000 0.9996001    26
## 5 Gradient Descent 0.9999992 0.9999983 32041
fw <- function (x) 10*sin(0.3*x)*sin(1.3*x^2) + 0.00001*x^4 + 0.2*x+80
plot(fw, -50, 50, n = 1000, main = "optim() minimising 'wild function'")

x0 <- c(1)

# Newton
o1 <- newton_optimize(fw, x0)

# Marquardt
o2 <- marquardt_optimize(fw,x0)

# DfP
B0<-diag(1)
o3 <- dfp_optimize(fw, x0, B0)

# Optim
o4 <- optim(par=x0, fn=fw, method="BFGS", control = list(trace=T, maxit=100000, temp = 20, parscale = 20))
## initial  value 83.047519 
## final  value 67.709261 
## converged
# Gradient Descent
o5 <- gradient_descent(obj_func=fw, init_x=x0, learning_rate=0.001)

data.frame(Method = c("Newton", "Marquardt", "DFP", "BFGS", "Gradient Descent"), 
           x1 = c(o1$x[1],o2$x[1],o3$x[1],o4$par[1],o5$x[1]), 
           func=c(fw(o1$x),fw(o2$x),fw(o3$x),fw(o4$par[1]),fw(o5$x)), 
           iter = c(o1$iter,o2$iter,o3$iter,o4$counts[2],o5$iter))
##             Method         x1     func iter
## 1           Newton   1.190594 83.60594    4
## 2        Marquardt   1.186752 83.60565    3
## 3              DFP  -1.190591 76.39410    1
## 4             BFGS -15.031766 67.70926    4
## 5 Gradient Descent  -1.190591 76.39410 2337