input: Write a python function to approximate weights for a given dataset using gradient descent.
  assert approximate_weights(np.array([[1, 2], [3, 4]]), np.array([5, 6])).shape == (2, 1)
  assert isinstance(approximate_weights(np.array([[2, 3], [4, 5]]), np.array([7, 8])), np.ndarray)
  assert approximate_weights(np.array([[1, 0], [0, 1]]), np.array([1, 1])).shape == (2, 1)
output: import numpy as np
  
  def approximate_weights(X, y, learning_rate=0.01, epochs=1000):
      m, n = X.shape
      weights = np.zeros((n, 1))
      y = y.reshape(-1, 1)
      
      for _ in range(epochs):
          predictions = X @ weights
          errors = predictions - y
          gradients = (1/m) * (X.T @ errors)
          weights -= learning_rate * gradients
      
      return weights