MiniTorch / tests / test_modules.py
test_modules.py
Raw
import random
from typing import List

from hypothesis import given
from hypothesis.strategies import DrawFn, composite, floats, integers, lists

import minitorch
from minitorch import Parameter, Scalar


@composite
def scalars(
    draw: DrawFn, min_value: float = -100000, max_value: float = 100000
) -> Scalar:
    val = draw(floats(min_value=min_value, max_value=max_value))
    return minitorch.Scalar(val)


class Network(minitorch.Module):
    def __init__(self) -> None:
        super().__init__()
        self.layer = ScalarLinear(2, 1)


class Network2(minitorch.Module):
    def __init__(self) -> None:
        super().__init__()
        self.layer1 = ScalarLinear(2, 2)
        self.layer2 = ScalarLinear(2, 1)


class ScalarLinear(minitorch.Module):
    def __init__(self, in_size: int, out_size: int) -> None:
        super().__init__()
        self.weights: List[List[Parameter]] = []
        self.bias: List[Parameter] = []
        for i in range(in_size):
            self.weights.append([])
            for j in range(out_size):
                self.weights[i].append(
                    self.add_parameter(
                        f"weight_{i}_{j}", minitorch.Scalar(2 * (random.random() - 0.5))
                    )
                )
        for j in range(out_size):
            self.bias.append(
                self.add_parameter(
                    f"bias_{j}", minitorch.Scalar(2 * (random.random() - 0.5))
                )
            )

    def forward(self, inputs: List[Scalar]) -> List[Scalar]:
        y = [b.value for b in self.bias]
        for i, x in enumerate(inputs):
            for j in range(len(y)):
                y[j] = y[j] + x * self.weights[i][j].value
        return y


@given(lists(scalars(), max_size=10), integers(min_value=5, max_value=20))
def test_linear(inputs: List[Scalar], out_size: int) -> None:
    lin = ScalarLinear(len(inputs), out_size)
    mid = lin.forward(inputs)
    lin2 = ScalarLinear(out_size, 1)
    lin2.forward(mid)


# @given(
#     lists(scalars(min_value=-10, max_value=10), min_size=2, max_size=2),
#     lists(scalars(min_value=-10, max_value=10), min_size=2, max_size=2),
# )
# def test_nn2(inputs, bias):
#     model = Network2()

#     def check(x1, x2, b1, b2):
#         model.layer1.bias[0].update(b1)
#         model.layer1.bias[1].update(b2)
#         return model.forward([x1, x2])

#     minitorch.derivative_check(check, *(inputs + bias))


def test_nn_size() -> None:
    model = Network2()
    assert len(model.parameters()) == (
        len(model.layer1.parameters()) + len(model.layer2.parameters())
    )

    assert model.layer2.bias[0].value.data != 0
    assert model.layer1.bias[0].value.data != 0
    assert model.layer1.weights[0][0].value.data != 0

    for p in model.parameters():
        p.update(minitorch.Scalar(0))

    assert model.layer2.bias[0].value.data == 0
    assert model.layer1.bias[0].value.data == 0
    assert model.layer1.weights[0][0].value.data == 0