text: import numpy as np
  import random
  import utils.mnist_loader as mnist_loader
  
  
  def sigmoid(z):
      return 1.0 / (1.0 + np.exp(-z))
  
  
  def sigmoid_derivative(z):
      return sigmoid(z) * (1 - sigmoid(z))
  
  
  class Network:
      def __init__(self, sizes):
          self.num_layers = len(sizes)
          self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
          self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
  
      def feedforward(self, a):
          for b, w in zip(self.biases, self.weights):
              a = sigmoid(np.dot(w, a) + b)
          return a
  
      def SGD(self, training_data, epochs, minibatch_size, eta, test_data=None):
          if test_data:
              n_test = len(test_data)
          n = len(training_data)
          for j in range(epochs):
              random.shuffle(training_data)
              minibatches = [
                  training_data[k : k + minibatch_size]
                  for k in range(0, n, minibatch_size)
              ]
              for minibatch in minibatches:
                  self.update(minibatch, eta)
              if test_data:
                  print(f"Epoch {j}: {self.evaluate(test_data)} / {n_test}")
              else:
                  print(f"Epoch {j} completed")
  
      def update(self, minibatch, eta):
          nabla_b = [np.zeros(b.shape) for b in self.biases]
          nabla_w = [np.zeros(w.shape) for w in self.weights]
          for x, y in minibatch:
              delta_nabla_b, delta_nabla_w = self.backprop(x, y)
              nabla_b = [nb + dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
              nabla_w = [nw + dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
          self.biases = [
              b - (eta / len(minibatch)) * nb for b, nb in zip(self.biases, nabla_b)
          ]
          self.weights = [
              w - (eta / len(minibatch)) * nw for w, nw in zip(self.weights, nabla_w)
          ]
  
      def backprop(self, x, y):
          nabla_b = [np.zeros(b.shape) for b in self.biases]
          nabla_w = [np.zeros(w.shape) for w in self.weights]
  
          activation = x
          activations = [x]
          zs = []
          for b, w in zip(self.biases, self.weights):
              z = np.dot(w, activation) + b
              zs.append(z)
              activation = sigmoid(z)
              activations.append(activation)
  
          delta = self.cost_derivative(activations[-1], y) * sigmoid_derivative(zs[-1])
          nabla_b[-1] = delta
          nabla_w[-1] = np.dot(delta, activations[-2].transpose())
  
          for l in range(2, self.num_layers):
              z = zs[-l]
              sd = sigmoid_derivative(z)
              delta = np.dot(self.weights[-l + 1].transpose(), delta) * sd
              nabla_b[-l] = delta
              nabla_w[-l] = np.dot(delta, activations[-l - 1].transpose())
  
          return nabla_b, nabla_w
  
      def cost_derivative(self, output_activations, y):
          return output_activations - y
  
      def evaluate(self, test_data):
          results = [(np.argmax(self.feedforward(x)), y) for x, y in test_data]
          return sum(int(y[x] == 1.0) for x, y in results)
  
  
  if __name__ == "__main__":
      training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
      print(type(training_data[0][0]))
      print(training_data[0][0].shape)
      # net = Network([784, 100, 10])
      # net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
  