input: Write a python function to implement a basic neural network for binary classification using sigmoid activation and gradient descent.
  assert neural_network_predictions([[1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], [[1], [0], [0], [1]]) in [[1, 0, 0, 1]]
  assert neural_network_predictions([[1, 1, 1], [0, 0, 0], [0, 1, 0], [1, 0, 1]], [[1], [0], [0], [1]]) in [[1, 0, 0, 1]]
  assert neural_network_predictions([[1, 0, 0], [0, 1, 1], [1, 0, 1], [0, 0, 1]], [[1], [0], [1], [0]]) in [[1, 0, 1, 0]]
output: import numpy as np
  
  def sigmoid(x):
      return 1 / (1 + np.exp(-x))
  
  def sigmoid_derivative(x):
      return x * (1 - x)
  
  def neural_network_predictions(X, y, epochs=10000, lr=0.1):
      X = np.array(X)
      y = np.array(y)
      
      input_layer_neurons = X.shape[1]  # Number of features
      hidden_layer_neurons = 2          # Number of hidden layer neurons
      output_neurons = 1                # Number of output neurons
      
      # Weight and bias initialization
      wh = np.random.uniform(size=(input_layer_neurons, hidden_layer_neurons))
      bh = np.random.uniform(size=(1, hidden_layer_neurons))
      wout = np.random.uniform(size=(hidden_layer_neurons, output_neurons))
      bout = np.random.uniform(size=(1, output_neurons))
      
      for epoch in range(epochs):
          # Forward Propagation
          hidden_layer_input = np.dot(X, wh) + bh
          hidden_layer_activation = sigmoid(hidden_layer_input)
          
          output_layer_input = np.dot(hidden_layer_activation, wout) + bout
          output = sigmoid(output_layer_input)
          
          # Backpropagation
          E = y - output
          slope_output_layer = sigmoid_derivative(output)
          slope_hidden_layer = sigmoid_derivative(hidden_layer_activation)
          
          d_output = E * slope_output_layer
          Error_at_hidden_layer = d_output.dot(wout.T)
          d_hidden_layer = Error_at_hidden_layer * slope_hidden_layer
          
          # Updating Weights and Biases
          wout += hidden_layer_activation.T.dot(d_output) * lr
          bout += np.sum(d_output, axis=0, keepdims=True) * lr
          wh += X.T.dot(d_hidden_layer) * lr
          bh += np.sum(d_hidden_layer, axis=0, keepdims=True) * lr
      
      # Predict
      hidden_layer_input = np.dot(X, wh) + bh
      hidden_layer_activation = sigmoid(hidden_layer_input)
      output_layer_input = np.dot(hidden_layer_activation, wout) + bout
      output = sigmoid(output_layer_input)
      predictions = [1 if o > 0.5 else 0 for o in output]
      return predictions