Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hw1 #49

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

hw1 #49

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions minitorch/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ def modules(self) -> Sequence[Module]:
def train(self) -> None:
"""Set the mode of this module and all descendent modules to `train`."""
# TODO: Implement for Task 0.4.
raise NotImplementedError("Need to implement for Task 0.4")
self.training = True
for module in self.modules():
module.train()

def eval(self) -> None:
"""Set the mode of this module and all descendent modules to `eval`."""
# TODO: Implement for Task 0.4.
raise NotImplementedError("Need to implement for Task 0.4")
self.training = False
for module in self.modules():
module.eval()

def named_parameters(self) -> Sequence[Tuple[str, Parameter]]:
"""Collect all the parameters of this module and its descendents.
Expand All @@ -48,12 +52,21 @@ def named_parameters(self) -> Sequence[Tuple[str, Parameter]]:

"""
# TODO: Implement for Task 0.4.
raise NotImplementedError("Need to implement for Task 0.4")
parameters_list = []
for param_name, param_value in self._parameters.items():
parameters_list.append((param_name, param_value))
for module_name, module in self._modules.items():
for sub_param_name, sub_param_value in module.named_parameters():
parameters_list.append((f"{module_name}.{sub_param_name}", sub_param_value))
return parameters_list

def parameters(self) -> Sequence[Parameter]:
"""Enumerate over all the parameters of this module and its descendents."""
# TODO: Implement for Task 0.4.
raise NotImplementedError("Need to implement for Task 0.4")
params = list(self._parameters.values())
for module in self.modules():
params.extend(module.parameters())
return params

def add_parameter(self, k: str, v: Any) -> Parameter:
"""Manually add a parameter. Useful helper for scalar parameters.
Expand Down
119 changes: 119 additions & 0 deletions minitorch/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# ## Task 0.1
from typing import Callable, Iterable


#
# Implementation of a prelude of elementary functions.

Expand Down Expand Up @@ -34,6 +35,88 @@

# TODO: Implement for Task 0.1.

def mul(x: float, y: float) -> float:
"""Multiplies two numbers."""
return x * y


def id(x: float) -> float:
"""Returns the input unchanged."""
return x


def add(x: float, y: float) -> float:
"""Adds two numbers."""
return x + y


def neg(x: float) -> float:
"""Negates a number."""
return -x


def lt(x: float, y: float) -> bool:
"""Checks if one number is less than another."""
return x < y


def eq(x: float, y: float) -> bool:
"""Checks if two numbers are equal."""
return x == y


def max(x: float, y: float) -> float:
"""Returns the larger of two numbers."""
return x if x > y else y


def is_close(x: float, y: float) -> bool:
"""Checks if two numbers are close in value."""
return abs(x - y) < 1e-2


def sigmoid(x: float) -> float:
"""Calculates the sigmoid function."""
if x >= 0:
return 1.0 / (1.0 + math.exp(-x))
else:
return math.exp(x) / (1.0 + math.exp(x))


def relu(x: float) -> float:
"""Applies the ReLU activation function."""
return max(0, x)


def log(x: float) -> float:
"""Calculates the natural logarithm."""
return math.log(x)


def exp(x: float) -> float:
"""Calculates the exponential function."""
return math.exp(x)


def inv(x: float) -> float:
"""Calculates the reciprocal."""
return 1 / x


def log_back(x: float, y: float) -> float:
"""Computes the derivative of log times a second arg."""
return y / x


def inv_back(x: float, y: float) -> float:
"""Computes the derivative of reciprocal times a second arg."""
return -y / (x ** 2)


def relu_back(x: float, y: float) -> float:
"""Computes the derivative of ReLU times a second arg."""
return y if x > 0 else 0


# ## Task 0.3

Expand All @@ -50,5 +133,41 @@
# - sum: sum lists
# - prod: take the product of lists

def map(func: Callable[[float], float], iterable: Iterable[float]) -> Iterable[float]:
"""Applies a given function to each element of an iterable"""
return [func(item) for item in iterable]


def zipWith(func: Callable[[float, float], float], iterable1: Iterable[float], iterable2: Iterable[float]) -> Iterable[
float]:
"""Combines elements from two iterables using a given function."""
return [func(a, b) for a, b in zip(iterable1, iterable2)]


def reduce(func: Callable[[float, float], float], iterable: Iterable[float], initial: float) -> float:
"""Reduces an iterable to a single float value using a given function."""
result = initial
for item in iterable:
result = func(result, item)
return result


def negList(iterable: Iterable[float]) -> Iterable[float]:
"""Negate all elements in a list using map"""
return map(neg, iterable)


def addLists(iterable1: Iterable[float], iterable2: Iterable[float]) -> Iterable[float]:
"""Add corresponding elements from two lists using zipWith"""
return zipWith(add, iterable1, iterable2)


def sum(iterable: Iterable[float]) -> float:
"""Sum all elements in a list using reduce"""
return reduce(add, iterable, 0)


def prod(iterable: Iterable[float]) -> float:
"""Calculate the product of all elements in a list using reduce"""
return reduce(mul, iterable, 1)
# TODO: Implement for Task 0.3.
33 changes: 22 additions & 11 deletions tests/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
add,
addLists,
eq,
exp,
id,
inv,
inv_back,
log,
log_back,
lt,
max,
Expand All @@ -22,11 +24,12 @@
prod,
relu,
relu_back,
sigmoid,
sigmoid
)

from .strategies import assert_close, small_floats


# ## Task 0.1 Basic hypothesis tests.


Expand Down Expand Up @@ -108,40 +111,48 @@ def test_sigmoid(a: float) -> None:
* It is strictly increasing.
"""
# TODO: Implement for Task 0.2.
raise NotImplementedError("Need to implement for Task 0.2")
assert 0.0 <= sigmoid(a) <= 1.0
assert abs((1.0 - sigmoid(a)) - sigmoid(-a)) < 1e-6
assert abs(sigmoid(0) - 0.5) < 1e-6
assert sigmoid(a) <= sigmoid(a + 1)


@pytest.mark.task0_2
@given(small_floats, small_floats, small_floats)
def test_transitive(a: float, b: float, c: float) -> None:
"""Test the transitive property of less-than (a < b and b < c implies a < c)"""
# TODO: Implement for Task 0.2.
raise NotImplementedError("Need to implement for Task 0.2")
if lt(a, b) and lt(b, c):
assert (lt(a, c))


@pytest.mark.task0_2
def test_symmetric() -> None:
@given(small_floats, small_floats)
def test_symmetric(a: float, b: float) -> None:
"""Write a test that ensures that :func:`minitorch.operators.mul` is symmetric, i.e.
gives the same value regardless of the order of its input.
"""
# TODO: Implement for Task 0.2.
raise NotImplementedError("Need to implement for Task 0.2")
assert_close(mul(a, b), mul(b, a))


@pytest.mark.task0_2
def test_distribute() -> None:
@given(small_floats, small_floats, small_floats)
def test_distribute(x: float, y: float, z: float) -> None:
r"""Write a test that ensures that your operators distribute, i.e.
:math:`z \times (x + y) = z \times x + z \times y`
"""
# TODO: Implement for Task 0.2.
raise NotImplementedError("Need to implement for Task 0.2")
assert_close(mul(z, add(x, y)), add(mul(z, x), mul(z, y)))


@pytest.mark.task0_2
def test_other() -> None:
@given(small_floats, small_floats)
def test_other(a: float, b: float) -> None:
"""Write a test that ensures some other property holds for your functions."""
# checks that eq output is the same no matter the order of args
# TODO: Implement for Task 0.2.
raise NotImplementedError("Need to implement for Task 0.2")
assert (eq(a, b) == eq(b, a))


# ## Task 0.3 - Higher-order functions
Expand All @@ -168,8 +179,8 @@ def test_sum_distribute(ls1: List[float], ls2: List[float]) -> None:
"""Write a test that ensures that the sum of `ls1` plus the sum of `ls2`
is the same as the sum of each element of `ls1` plus each element of `ls2`.
"""
assert_close(sum(ls1) + sum(ls2), sum(addLists(ls1, ls2)))
# TODO: Implement for Task 0.3.
raise NotImplementedError("Need to implement for Task 0.3")


@pytest.mark.task0_3
Expand Down Expand Up @@ -210,7 +221,7 @@ def test_one_args(fn: Tuple[str, Callable[[float], float]], t1: float) -> None:
@given(small_floats, small_floats)
@pytest.mark.parametrize("fn", two_arg)
def test_two_args(
fn: Tuple[str, Callable[[float, float], float]], t1: float, t2: float
fn: Tuple[str, Callable[[float, float], float]], t1: float, t2: float
) -> None:
name, base_fn = fn
base_fn(t1, t2)
Expand Down