input: Write a python function to implement stochastic gradient descent (SGD) for linear regression.
  assert np.allclose(sgd(np.array([[1,3], [2,5], [3,7], [4,11]]), np.array([8,13,18,23]), np.zeros(2), 0.01, 4, 1000), [-0.14617507, 3.92050762], atol=1e-5)
  assert np.allclose(sgd(np.array([[1,2], [2,4], [3,6]]), np.array([6,10,14]), np.zeros(2), 0.01, 3, 1000), [0.000138, 2.000000], atol=1e-5)
  assert np.allclose(sgd(np.array([[1,5], [2,10], [3,15], [4,20]]), np.array([12,24,36,48]), np.zeros(2), 0.01, 4, 1000), [0.000361, 2.400000], atol=1e-5)
output: import numpy as np
  
  def sgd(X, y, weights, learning_rate, batch_size, epochs):
      n_samples, n_features = X.shape
      
      for epoch in range(epochs):
          for i in range(0, n_samples, batch_size):
              X_batch = X[i:i + batch_size]
              y_batch = y[i:i + batch_size]
  
              predictions = X_batch.dot(weights)
              errors = predictions - y_batch
              gradient = (X_batch.T.dot(errors)) / batch_size
  
              weights -= learning_rate * gradient
  
      return weights