input: Write a python function to perform gradient descent optimization.
  assert np.allclose(gradient_descent_optimizer(np.array([[1, 2], [1, 3], [1, 4], [1, 5]]), np.array([7, 6, 5, 4]), np.array([0.1, 0.2]), 0.01, 1000), np.array([8.349, -1.172]), atol=1e-2)
  assert np.allclose(gradient_descent_optimizer(np.array([[1, 1], [1, 2], [1, 3]]), np.array([1, 2, 3]), np.array([0, 0]), 0.1, 100), np.array([0, 1]), atol=1e-2)
  assert np.allclose(gradient_descent_optimizer(np.array([[1, 1], [1, 2], [1, 3], [1, 4]]), np.array([2, 4, 6, 8]), np.array([0.5, 0.5]), 0.01, 500), np.array([1.111, 1.944]), atol=1e-2)
output: import numpy as np
  
  def gradient_descent_optimizer(X, y, theta, learning_rate, num_iterations):
      m = len(y)
      for _ in range(num_iterations):
          predictions = X.dot(theta)
          errors = predictions - y
          gradient = (1/m) * X.T.dot(errors)
          theta -= learning_rate * gradient
      return theta