# Path: scratch/neural_networks.py
# def sigmoid(t: float) -> float:
#     return 1 / (1 + math.exp(-t))
# 
# Path: scratch/probability.py
# def inverse_normal_cdf(p: float,
#                        mu: float = 0,
#                        sigma: float = 1,
#                        tolerance: float = 0.00001) -> float:
#     """Find approximate inverse using binary search"""
# 
#     # if not standard, compute standard and rescale
#     if mu != 0 or sigma != 1:
#         return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)
# 
#     low_z = -10.0                      # normal_cdf(-10) is (very close to) 0
#     hi_z  =  10.0                      # normal_cdf(10)  is (very close to) 1
#     while hi_z - low_z > tolerance:
#         mid_z = (low_z + hi_z) / 2     # Consider the midpoint
#         mid_p = normal_cdf(mid_z)      # and the cdf's value there
#         if mid_p < p:
#             low_z = mid_z              # Midpoint too low, search above it
#         else:
#             hi_z = mid_z               # Midpoint too high, search below it
# 
#     return mid_z
# 
# Path: scratch/linear_algebra.py
# def dot(v: Vector, w: Vector) -> float:
#     """Computes v_1 * w_1 + ... + v_n * w_n"""
#     assert len(v) == len(w), "vectors must be same length"
# 
#     return sum(v_i * w_i for v_i, w_i in zip(v, w))
# 
# Path: scratch/linear_algebra.py
# def squared_distance(v: Vector, w: Vector) -> float:
#     """Computes (v_1 - w_1) ** 2 + ... + (v_n - w_n) ** 2"""
#     return sum_of_squares(subtract(v, w))


from typing import List
from typing import Callable
from typing import Iterable, Tuple
from scratch.neural_networks import sigmoid
from scratch.probability import inverse_normal_cdf
from scratch.linear_algebra import dot
from typing import List
from scratch.linear_algebra import squared_distance
    from scratch.neural_networks import binary_encode, fizz_buzz_encode, argmax
import operator
import random
import math
import json
    import tqdm
    import mnist
    import matplotlib.pyplot as plt
    import tqdm

    def backward(self, gradient):
        """
        Similarly, we're not going to be prescriptive about what the
        gradient looks like. It's up to you the user to make sure
        that you're doing things sensibly.
        """
        raise NotImplementedError

    def params(self) -> Iterable[Tensor]:
        """
        Returns the parameters of this layer. The default implementation
        returns nothing, so that if you have a layer with no parameters
        you don't have to implement this.
        """
        return ()

    def grads(self) -> Iterable[Tensor]:
        """
        Returns the gradients, in the same order as params()
        """
        return ()


class Sigmoid(Layer):
    def forward(self, input: Tensor) -> Tensor:
        """
        Apply sigmoid to each element of the input tensor,
        and save the results to use in backpropagation.
        """

# Path: scratch/neural_networks.py
# def sigmoid(t: float) -> float:
#     return 1 / (1 + math.exp(-t))
# 
# Path: scratch/probability.py
# def inverse_normal_cdf(p: float,
#                        mu: float = 0,
#                        sigma: float = 1,
#                        tolerance: float = 0.00001) -> float:
#     """Find approximate inverse using binary search"""
# 
#     # if not standard, compute standard and rescale
#     if mu != 0 or sigma != 1:
#         return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)
# 
#     low_z = -10.0                      # normal_cdf(-10) is (very close to) 0
#     hi_z  =  10.0                      # normal_cdf(10)  is (very close to) 1
#     while hi_z - low_z > tolerance:
#         mid_z = (low_z + hi_z) / 2     # Consider the midpoint
#         mid_p = normal_cdf(mid_z)      # and the cdf's value there
#         if mid_p < p:
#             low_z = mid_z              # Midpoint too low, search above it
#         else:
#             hi_z = mid_z               # Midpoint too high, search below it
# 
#     return mid_z
# 
# Path: scratch/linear_algebra.py
# def dot(v: Vector, w: Vector) -> float:
#     """Computes v_1 * w_1 + ... + v_n * w_n"""
#     assert len(v) == len(w), "vectors must be same length"
# 
#     return sum(v_i * w_i for v_i, w_i in zip(v, w))
# 
# Path: scratch/linear_algebra.py
# def squared_distance(v: Vector, w: Vector) -> float:
#     """Computes (v_1 - w_1) ** 2 + ... + (v_n - w_n) ** 2"""
#     return sum_of_squares(subtract(v, w))


# Path: scratch/deep_learning.py
from typing import List
from typing import Callable
from typing import Iterable, Tuple
from scratch.neural_networks import sigmoid
from scratch.probability import inverse_normal_cdf
from scratch.linear_algebra import dot
from typing import List
from scratch.linear_algebra import squared_distance
    from scratch.neural_networks import binary_encode, fizz_buzz_encode, argmax
import operator
import random
import math
import json
    import tqdm
    import mnist
    import matplotlib.pyplot as plt
    import tqdm


    def backward(self, gradient):
        """
        Similarly, we're not going to be prescriptive about what the
        gradient looks like. It's up to you the user to make sure
        that you're doing things sensibly.
        """
        raise NotImplementedError

    def params(self) -> Iterable[Tensor]:
        """
        Returns the parameters of this layer. The default implementation
        returns nothing, so that if you have a layer with no parameters
        you don't have to implement this.
        """
        return ()

    def grads(self) -> Iterable[Tensor]:
        """
        Returns the gradients, in the same order as params()
        """
        return ()


class Sigmoid(Layer):
    def forward(self, input: Tensor) -> Tensor:
        """
        Apply sigmoid to each element of the input tensor,
        and save the results to use in backpropagation.
        """

        self.sigmoids = tensor_apply(sigmoid, input)