ub-|9;Hst7<{O;LrE{> zpo*wLo<75+gZotn)AS{BdzQiCj%0I|o8_??!;r{G7$3g+puE9u4j`a30yZ<6s-9HIc; zmu%{pkKgH7e0 y$$o_R;&UVR2pu~jeNMGx*tFp7WAqcl=9wP6ou#i7Jb&+%(M z!${HQ+I!!aT}5< BQc9zJ^ZI$&ft@on>DUuQeIy zZazb&>14pt%+WkQ5Z9red1s}fzSGuf)!`G3!At+uHb13tew4w!n&x$Z!kzJ{D19EK zPSa4r9&zJ;rgiRbaZ2v_44tNvf%Cx~SM@*@>Ii#hGeEaTDN2sMqBF7eI4(G!VWeqT zy!S@(xaY~UDag&cH^&^+pvz{ED)9(=;l@R}yApV8_uQ%;We|~1i;qv)TV@jN$uQD% ze2>0h1SR$4j(v~fvc^yM#w9z|%;|Wyj98lM^R8jv(4&+cg*~LGd9tZz`mE#gTpDSb zJILa5k>9gaU8cr-hLNVREYT>A%JL*2cUtpv8oDEt;u+Lt)v&@lT-r0IbzzjjCw6$I zrupXJPtcpMAqwiaWa^9ee&&-sCZtp}uLr;AYv6NGq2u_qI&_{J&hG?r57pNI!Q6d9 z<~{9_&%SS#O;$Fl0#Z=P?8%(VKG~GplY{s4bzn}AakrH{fK0l+r`sc4!v=5iYzD92 z&f%)u^GJ=9ZBJ 4<8!>g%sUi%f zj(PX<%f5oBEWXDt-tpBe^K16)(0=NKv5 zPD9K*ZOxQ`s_$FZ9!}|9-wW~0RB_WYk?r&M^ Y zF9YFiAl=66G0wVfpOeG;7U_sC{t;d7D)QgzIJMK?epKasJGW}BTQ>Ld?3V)lHbs99 z{B^uvTb#$hg{@YP68)n K-!a5?X11Wudj3~De!Ed;FrUx>wBJhX%cIM>a9CC5` znxXaiEA_$(M@0ya)Q2LtgF00lx)&V6^XmK}a5K16sJl9yiq-B@`2&wR;`0aUcx ?$zw6e+&n~*pfK(rM $< c#jFdJ#7xDJSUwwbbbCxzwp%tSExyaIK*q~ z1TOW@U+LF`fn@^dE@yLYwKCKC=#_e*t4=bY0l0hzmuK!)SEo|%J|Fo#JRH&s*>LMx zy%j!g9@z;WN8-gW)ljc>0Gs;!q@ZI2^H*kSlqul+aSL(vquT>>yN-aH`s2jWu)Qcc zFvt24Dh Y)o&5nzxyHT~(L_IkO3S&=(^FN_ zS-!E~mt}(XBUEb6rkJ^?_Svb;H92J_cO&4@Q41pEPS1S@E#PhTx_Dk6zfv=NywO#R z)o0#81@yq7`VlHMi-4V~Le)OUi}b_<>f={h4*sq)y?_au-^r)aTCD_CsC!`7FbA+c zzL(h<2k&bw``~A-$stoX8cqf}uICusuTeFVgO5AeP~gWg)Qj@^TFb^?WnjCa;cQzu zJJs=64${wIre+GkOmipvj1$>`DXt%(QgiOiR0wnSeyC4AbEj?u-#RjBzxOlActv(z z`Ph5atzRX(LLGtggsZq8J&2$`6xh$9(z6`)*`FE8FS5_bTJ(N|O3{g+>YNYX{5R)M zFZzo1&ALkQYrWp9AJ (6?SgDzky-@{YxVdt|I^Q(S@O4Ese`6A?QCoagV z#5>c6YCemQU6>xQKhG!hV)xg64wa@8A>84trUXWCJ+ X*Y!c5b8tm<}*cwIWdrF=TWrkSdBIy- GokG+pE55i=b*9pT%N<^{>K%kI+Mdp*BnKcs9VqDQZg2yKO^K` zS1C__!zU^T^E_FCe 9rvfSgwx6_4uJ`-c6?978G z0(M^bjaB_hh3gfcMBsjY+GTRzXX3eXCc;eFSmdm^k2XQMRkndM_%l_*$5yyF#Z_tC zGuQg*66PPD{b1j$)7dO3A+ac%d+_i6UY2jyUC2bsq`(Fd)yW1@JqexVFjIB*3>?ao z3xl|cSY2P@!?PM?I_pB_f=)ZVDc+w#IbmI+xV0*}cgsD5eJoe{Im{KEJFVP7;6t<_ zGg+Kr>rBzuNof${;20H`>Gv6W-G^)!o-4&q_i1PL<({HjC(j3GG14XdRV)61cf0EP ze4^BO3>DXfJgZcvOd}`jyKDZ>TE(b5Pf|
o3Kjlc9`%%*ibBYTafCh|yLYA2U|jG4BR zhxvf5;VtIE$J{gBhEelLm$EhCmL7zw;sLt>ye4+|@&ZGPb{ua|8 literal 0 HcmV?d00001 diff --git a/minitorch/autodiff.py b/minitorch/autodiff.py index 2b69873b..7f90bf24 100644 --- a/minitorch/autodiff.py +++ b/minitorch/autodiff.py @@ -22,8 +22,16 @@ def central_difference(f: Any, *vals: Any, arg: int = 0, epsilon: float = 1e-6) Returns: An approximation of $f'_i(x_0, \ldots, x_{n-1})$ """ - # TODO: Implement for Task 1.1. - raise NotImplementedError("Need to implement for Task 1.1") + vals_pos = list(vals) + vals_neg = list(vals) + + vals_pos[arg] = vals[arg] + epsilon + vals_neg[arg] = vals[arg] - epsilon + + f_pos = f(*vals_pos) + f_neg = f(*vals_neg) + + return (f_pos - f_neg) / (2.0 * epsilon) variable_count = 1 @@ -61,8 +69,22 @@ def topological_sort(variable: Variable) -> Iterable[Variable]: Returns: Non-constant Variables in topological order starting from the right. """ - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + visited = set() + post: List[Variable] = [] + + def dfs(v: Variable) -> None: + uid = v.unique_id + if uid in visited: + return + visited.add(uid) + if v.is_constant(): + return + for p in v.parents: + dfs(p) + post.append(v) + + dfs(variable) + return list(reversed(post)) def backpropagate(variable: Variable, deriv: Any) -> None: @@ -76,8 +98,20 @@ def backpropagate(variable: Variable, deriv: Any) -> None: No return. Should write to its results to the derivative values of each leaf through `accumulate_derivative`. """ - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + topo = list(topological_sort(variable)) + + grads: dict[int, float] = {} + grads[variable.unique_id] = float(deriv) + + for v in topo: + g_out = float(grads.get(v.unique_id, 0.0)) + if v.is_leaf(): + v.accumulate_derivative(g_out) + continue + + for parent, g_local in v.chain_rule(g_out): + pid = parent.unique_id + grads[pid] = grads.get(pid, 0.0) + float(g_local) @dataclass diff --git a/minitorch/module.py b/minitorch/module.py index 11fc1f39..dcdcffab 100644 --- a/minitorch/module.py +++ b/minitorch/module.py @@ -1,14 +1,14 @@ from __future__ import annotations -from typing import Any, Dict, Optional, Sequence, Tuple +from typing import Any, Dict, Optional, Sequence, Tuple, List class Module: - """ - Modules form a tree that store parameters and other + """Modules form a tree that store parameters and other submodules. They make up the basis of neural network stacks. - Attributes: + Attributes + ---------- _modules : Storage of the child modules _parameters : Storage of the module's parameters training : Whether the module is in training mode or evaluation mode @@ -25,42 +25,57 @@ def __init__(self) -> None: self.training = True def modules(self) -> Sequence[Module]: - "Return the direct child modules of this module." + """Return the direct child modules of this module.""" m: Dict[str, Module] = self.__dict__["_modules"] return list(m.values()) def train(self) -> None: - "Set the mode of this module and all descendent modules to `train`." - raise NotImplementedError("Need to include this file from past assignment.") + """Set the mode of this module and all descendent modules to `train`.""" + self.training = True + for child in self._modules.values(): + child.train() def eval(self) -> None: - "Set the mode of this module and all descendent modules to `eval`." - raise NotImplementedError("Need to include this file from past assignment.") + """Set the mode of this module and all descendent modules to `eval`.""" + self.training = False + for child in self._modules.values(): + child.eval() def named_parameters(self) -> Sequence[Tuple[str, Parameter]]: - """ - Collect all the parameters of this module and its descendents. + """Collect all the parameters of this module and its descendents. - - Returns: + Returns + ------- The name and `Parameter` of each ancestor parameter. + """ - raise NotImplementedError("Need to include this file from past assignment.") + out: List[Tuple[str, Parameter]] = [] + + for name, p in self._parameters.items(): + out.append((name, p)) + + for child_name, child_mod in self._modules.items(): + for sub_name, p in child_mod.named_parameters(): + out.append((f"{child_name}.{sub_name}", p)) + + return out def parameters(self) -> Sequence[Parameter]: - "Enumerate over all the parameters of this module and its descendents." - raise NotImplementedError("Need to include this file from past assignment.") + """Enumerate over all the parameters of this module and its descendents.""" + return [p for _, p in self.named_parameters()] def add_parameter(self, k: str, v: Any) -> Parameter: - """ - Manually add a parameter. Useful helper for scalar parameters. + """Manually add a parameter. Useful helper for scalar parameters. Args: + ---- k: Local name of the parameter. v: Value for the parameter. Returns: + ------- Newly created parameter. + """ val = Parameter(v, k) self.__dict__["_parameters"][k] = val @@ -114,8 +129,7 @@ def _addindent(s_: str, numSpaces: int) -> str: class Parameter: - """ - A Parameter is a special container stored in a `Module`. + """A Parameter is a special container stored in a `Module`. It is designed to hold a `Variable`, but we allow it to hold any value for testing. @@ -130,7 +144,7 @@ def __init__(self, x: Any, name: Optional[str] = None) -> None: self.value.name = self.name def update(self, x: Any) -> None: - "Update the parameter value." + """Update the parameter value.""" self.value = x if hasattr(x, "requires_grad_"): self.value.requires_grad_(True) diff --git a/minitorch/operators.py b/minitorch/operators.py index 895ae82d..efba54d9 100644 --- a/minitorch/operators.py +++ b/minitorch/operators.py @@ -1,185 +1,164 @@ -""" -Collection of the core mathematical operators used throughout the code base. -""" +"""Collection of the core mathematical operators used throughout the code base.""" import math -from typing import Callable, Iterable # ## Task 0.1 +from typing import Callable, Iterable, Iterator, List, TypeVar, Sequence + # # Implementation of a prelude of elementary functions. +# Mathematical functions: +# - mul +# - id +# - add +# - neg +# - lt +# - eq +# - max +# - is_close +# - sigmoid +# - relu +# - log +# - exp +# - log_back +# - inv +# - inv_back +# - relu_back +# +# For sigmoid calculate as: +# $f(x) = \frac{1.0}{(1.0 + e^{-x})}$ if x >=0 else $\frac{e^x}{(1.0 + e^{x})}$ +# For is_close: +# $f(x) = |x - y| < 1e-2$ + def mul(x: float, y: float) -> float: - "$f(x, y) = x * y$" - raise NotImplementedError("Need to include this file from past assignment.") + return x * y def id(x: float) -> float: - "$f(x) = x$" - raise NotImplementedError("Need to include this file from past assignment.") + return x def add(x: float, y: float) -> float: - "$f(x, y) = x + y$" - raise NotImplementedError("Need to include this file from past assignment.") + return x + y def neg(x: float) -> float: - "$f(x) = -x$" - raise NotImplementedError("Need to include this file from past assignment.") + return -x def lt(x: float, y: float) -> float: - "$f(x) =$ 1.0 if x is less than y else 0.0" - raise NotImplementedError("Need to include this file from past assignment.") + return 1.0 if x < y else 0.0 def eq(x: float, y: float) -> float: - "$f(x) =$ 1.0 if x is equal to y else 0.0" - raise NotImplementedError("Need to include this file from past assignment.") + return 1.0 if x == y else 0.0 def max(x: float, y: float) -> float: - "$f(x) =$ x if x is greater than y else y" - raise NotImplementedError("Need to include this file from past assignment.") + return x if x > y else y -def is_close(x: float, y: float) -> float: - "$f(x) = |x - y| < 1e-2$" - raise NotImplementedError("Need to include this file from past assignment.") +def is_close(x: float, y: float) -> bool: + return abs(x - y) < 1e-2 def sigmoid(x: float) -> float: - r""" - $f(x) = \frac{1.0}{(1.0 + e^{-x})}$ - - (See https://en.wikipedia.org/wiki/Sigmoid_function ) - - Calculate as - - $f(x) = \frac{1.0}{(1.0 + e^{-x})}$ if x >=0 else $\frac{e^x}{(1.0 + e^{x})}$ - - for stability. - """ - raise NotImplementedError("Need to include this file from past assignment.") + if x >= 0.0: + z = math.exp(-x) + return 1.0 / (1.0 + z) + else: + z = math.exp(x) + return z / (1.0 + z) def relu(x: float) -> float: - """ - $f(x) =$ x if x is greater than 0, else 0 - - (See https://en.wikipedia.org/wiki/Rectifier_(neural_networks) .) - """ - raise NotImplementedError("Need to include this file from past assignment.") - - -EPS = 1e-6 + return x if x > 0.0 else 0.0 def log(x: float) -> float: - "$f(x) = log(x)$" - return math.log(x + EPS) + return math.log(x) def exp(x: float) -> float: - "$f(x) = e^{x}$" return math.exp(x) -def log_back(x: float, d: float) -> float: - r"If $f = log$ as above, compute $d \times f'(x)$" - raise NotImplementedError("Need to include this file from past assignment.") +def inv(x: float) -> float: + return 1.0 / x -def inv(x: float) -> float: - "$f(x) = 1/x$" - raise NotImplementedError("Need to include this file from past assignment.") +def log_back(a: float, b: float) -> float: + return b / a -def inv_back(x: float, d: float) -> float: - r"If $f(x) = 1/x$ compute $d \times f'(x)$" - raise NotImplementedError("Need to include this file from past assignment.") +def inv_back(a: float, b: float) -> float: + return -b / (a * a) -def relu_back(x: float, d: float) -> float: - r"If $f = relu$ compute $d \times f'(x)$" - raise NotImplementedError("Need to include this file from past assignment.") +def relu_back(a: float, b: float) -> float: + return b if a > 0.0 else 0.0 # ## Task 0.3 # Small practice library of elementary higher-order functions. +# Implement the following core functions +# - map +# - zipWith +# - reduce +# +# Use these to implement +# - negList : negate a list +# - addLists : add two lists together +# - sum: sum lists +# - prod: take the product of lists -def map(fn: Callable[[float], float]) -> Callable[[Iterable[float]], Iterable[float]]: - """ - Higher-order map. - - See https://en.wikipedia.org/wiki/Map_(higher-order_function) - - Args: - fn: Function from one value to one value. - - Returns: - A function that takes a list, applies `fn` to each element, and returns a - new list - """ - raise NotImplementedError("Need to include this file from past assignment.") - - -def negList(ls: Iterable[float]) -> Iterable[float]: - "Use `map` and `neg` to negate each element in `ls`" - raise NotImplementedError("Need to include this file from past assignment.") - - -def zipWith( - fn: Callable[[float, float], float] -) -> Callable[[Iterable[float], Iterable[float]], Iterable[float]]: - """ - Higher-order zipwith (or map2). - See https://en.wikipedia.org/wiki/Map_(higher-order_function) +T = TypeVar("T") +U = TypeVar("U") - Args: - fn: combine two values - Returns: - Function that takes two equally sized lists `ls1` and `ls2`, produce a new list by - applying fn(x, y) on each pair of elements. +def map(fn: Callable[[T], U], it: Iterable[T]) -> List[U]: + out: List[U] = [] + for v in it: + out.append(fn(v)) + return out - """ - raise NotImplementedError("Need to include this file from past assignment.") +def zipWith(fn: Callable[[T, U], T], a: Iterable[T], b: Iterable[U]) -> List[T]: + out: List[T] = [] + ia = iter(a) + ib = iter(b) + while True: + try: + va = next(ia) + vb = next(ib) + except StopIteration: + break + out.append(fn(va, vb)) + return out -def addLists(ls1: Iterable[float], ls2: Iterable[float]) -> Iterable[float]: - "Add the elements of `ls1` and `ls2` using `zipWith` and `add`" - raise NotImplementedError("Need to include this file from past assignment.") +def reduce(fn: Callable[[T, T], T], it: Iterable[T], start: T) -> T: + acc: T = start + for v in it: + acc = fn(acc, v) + return acc -def reduce( - fn: Callable[[float, float], float], start: float -) -> Callable[[Iterable[float]], float]: - r""" - Higher-order reduce. +def negList(ls: Iterable[float]) -> List[float]: + return map(neg, ls) - Args: - fn: combine two values - start: start value $x_0$ - Returns: - Function that takes a list `ls` of elements - $x_1 \ldots x_n$ and computes the reduction :math:`fn(x_3, fn(x_2, - fn(x_1, x_0)))` - """ - raise NotImplementedError("Need to include this file from past assignment.") +def addLists(a: Iterable[float], b: Iterable[float]) -> List[float]: + return zipWith(add, a, b) def sum(ls: Iterable[float]) -> float: - "Sum up a list using `reduce` and `add`." - raise NotImplementedError("Need to include this file from past assignment.") + return reduce(add, ls, 0.0) def prod(ls: Iterable[float]) -> float: - "Product of a list using `reduce` and `mul`." - raise NotImplementedError("Need to include this file from past assignment.") + return reduce(mul, ls, 1.0) diff --git a/minitorch/scalar.py b/minitorch/scalar.py index f5abbe9e..6e4b1ec6 100644 --- a/minitorch/scalar.py +++ b/minitorch/scalar.py @@ -92,31 +92,25 @@ def __rtruediv__(self, b: ScalarLike) -> Scalar: return Mul.apply(b, Inv.apply(self)) def __add__(self, b: ScalarLike) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return Add.apply(self, b) def __bool__(self) -> bool: return bool(self.data) def __lt__(self, b: ScalarLike) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return LT.apply(self, b) def __gt__(self, b: ScalarLike) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return LT.apply(b, self) def __eq__(self, b: ScalarLike) -> Scalar: # type: ignore[override] - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return EQ.apply(self, b) def __sub__(self, b: ScalarLike) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return Add.apply(self, Neg.apply(b)) def __neg__(self) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return Neg.apply(self) def __radd__(self, b: ScalarLike) -> Scalar: return self + b @@ -125,20 +119,16 @@ def __rmul__(self, b: ScalarLike) -> Scalar: return self * b def log(self) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return Log.apply(self) def exp(self) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return Exp.apply(self) def sigmoid(self) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return Sigmoid.apply(self) def relu(self) -> Scalar: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return ReLU.apply(self) # Variable elements for backprop @@ -173,8 +163,13 @@ def chain_rule(self, d_output: Any) -> Iterable[Tuple[Variable, Any]]: assert h.last_fn is not None assert h.ctx is not None - # TODO: Implement for Task 1.3. - raise NotImplementedError("Need to implement for Task 1.3") + local_grads = h.last_fn._backward(h.ctx, d_output) + + out: list[tuple[Variable, Any]] = [] + for inp, g in zip(h.inputs, local_grads): + if not inp.is_constant(): + out.append((inp, g)) + return out def backward(self, d_output: Optional[float] = None) -> None: """ diff --git a/minitorch/scalar_functions.py b/minitorch/scalar_functions.py index d8d2307b..e9e1f5e1 100644 --- a/minitorch/scalar_functions.py +++ b/minitorch/scalar_functions.py @@ -103,13 +103,13 @@ class Mul(ScalarFunction): @staticmethod def forward(ctx: Context, a: float, b: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") - + ctx.save_for_backward(a, b) + return float(operators.mul(a, b)) + @staticmethod def backward(ctx: Context, d_output: float) -> Tuple[float, float]: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + a, b = ctx.saved_values + return float(d_output * b), float(d_output * a) class Inv(ScalarFunction): @@ -117,13 +117,13 @@ class Inv(ScalarFunction): @staticmethod def forward(ctx: Context, a: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + ctx.save_for_backward(a) + return float(operators.inv(a)) @staticmethod def backward(ctx: Context, d_output: float) -> float: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + (a,) = ctx.saved_values + return float(operators.inv_back(a, d_output)) class Neg(ScalarFunction): @@ -131,13 +131,11 @@ class Neg(ScalarFunction): @staticmethod def forward(ctx: Context, a: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return float(operators.neg(a)) @staticmethod def backward(ctx: Context, d_output: float) -> float: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + return float(-d_output) class Sigmoid(ScalarFunction): @@ -145,13 +143,14 @@ class Sigmoid(ScalarFunction): @staticmethod def forward(ctx: Context, a: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + s = operators.sigmoid(a) + ctx.save_for_backward(s) + return float(s) @staticmethod def backward(ctx: Context, d_output: float) -> float: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + (s,) = ctx.saved_values + return float(d_output * s * (1.0 - s)) class ReLU(ScalarFunction): @@ -159,13 +158,13 @@ class ReLU(ScalarFunction): @staticmethod def forward(ctx: Context, a: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + ctx.save_for_backward(a) + return float(operators.relu(a)) @staticmethod def backward(ctx: Context, d_output: float) -> float: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + (a,) = ctx.saved_values + return float(operators.relu_back(a, d_output)) class Exp(ScalarFunction): @@ -173,13 +172,14 @@ class Exp(ScalarFunction): @staticmethod def forward(ctx: Context, a: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + e = operators.exp(a) + ctx.save_for_backward(e) + return float(e) @staticmethod def backward(ctx: Context, d_output: float) -> float: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + (e,) = ctx.saved_values + return float(d_output * e) class LT(ScalarFunction): @@ -187,13 +187,11 @@ class LT(ScalarFunction): @staticmethod def forward(ctx: Context, a: float, b: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return float(operators.lt(a, b)) @staticmethod def backward(ctx: Context, d_output: float) -> Tuple[float, float]: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + return 0.0, 0.0 class EQ(ScalarFunction): @@ -201,10 +199,8 @@ class EQ(ScalarFunction): @staticmethod def forward(ctx: Context, a: float, b: float) -> float: - # TODO: Implement for Task 1.2. - raise NotImplementedError("Need to implement for Task 1.2") + return float(operators.eq(a, b)) @staticmethod def backward(ctx: Context, d_output: float) -> Tuple[float, float]: - # TODO: Implement for Task 1.4. - raise NotImplementedError("Need to implement for Task 1.4") + return 0.0, 0.0 diff --git a/project/run_scalar.py b/project/run_scalar.py index 7ce5207b..2afef78b 100644 --- a/project/run_scalar.py +++ b/project/run_scalar.py @@ -10,8 +10,9 @@ class Network(minitorch.Module): def __init__(self, hidden_layers): super().__init__() - # TODO: Implement for Task 1.5. - raise NotImplementedError("Need to implement for Task 1.5") + self.layer1 = Linear(in_size=2, out_size=hidden_layers) + self.layer2 = Linear(in_size=hidden_layers, out_size=hidden_layers) + self.layer3 = Linear(in_size=hidden_layers, out_size=1) def forward(self, x): middle = [h.relu() for h in self.layer1.forward(x)] @@ -40,9 +41,15 @@ def __init__(self, in_size, out_size): ) def forward(self, inputs): - # TODO: Implement for Task 1.5. - raise NotImplementedError("Need to implement for Task 1.5") - + xs = list(inputs) + out = [] + out_size = len(self.bias) + for j in range(out_size): + s = self.bias[j].value + for i, x in enumerate(xs): + s = s + x * self.weights[i][j].value + out.append(s) + return out def default_log_fn(epoch, total_loss, correct, losses): print("Epoch ", epoch, " loss ", total_loss, "correct", correct) @@ -101,7 +108,7 @@ def train(self, data, learning_rate, max_epochs=500, log_fn=default_log_fn): if __name__ == "__main__": PTS = 50 - HIDDEN = 2 + HIDDEN = 6 RATE = 0.5 - data = minitorch.datasets["Simple"](PTS) - ScalarTrain(HIDDEN).train(data, RATE) + data = minitorch.datasets["Split"](PTS) + ScalarTrain(HIDDEN).train(data, RATE, max_epochs=800) \ No newline at end of file From 76657090199d5a6d6dee646703f4ff195d723675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9F=D0=B0=D0=BB=D1=8B?= =?UTF-8?q?=D1=81=D0=B0=D0=B5=D0=B2?= Date: Sun, 19 Oct 2025 23:45:10 +0300 Subject: [PATCH 6/8] workflows --- .github/workflows/minitorch.yml | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/minitorch.yml diff --git a/.github/workflows/minitorch.yml b/.github/workflows/minitorch.yml new file mode 100644 index 00000000..923052da --- /dev/null +++ b/.github/workflows/minitorch.yml @@ -0,0 +1,40 @@ +name: CI (Module 1) + +on: + push: + pull_request: + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y graphviz + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -e . + pip install pytest hypothesis flake8 pep8-naming + + - name: Lint with flake8 + run: | + # если есть .flake8 в корне — flake8 прочитает правила из него + flake8 minitorch/ tests/ project/ + + - name: Run tests (Module 1 only) + run: | + echo "Module 1" + pytest -q -m task1_1 + pytest -q -m task1_2 + pytest -q -m task1_3 + pytest -q -m task1_4 From 3d29208d9581364f0a07f2203068cfd8be21ae41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9F=D0=B0=D0=BB=D1=8B?= =?UTF-8?q?=D1=81=D0=B0=D0=B5=D0=B2?= Date: Sun, 19 Oct 2025 23:50:42 +0300 Subject: [PATCH 7/8] workflows --- .github/workflows/minitorch.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/minitorch.yml b/.github/workflows/minitorch.yml index 923052da..776051ae 100644 --- a/.github/workflows/minitorch.yml +++ b/.github/workflows/minitorch.yml @@ -5,36 +5,41 @@ on: pull_request: jobs: - tests: + build: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Python + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: "3.11" - cache: "pip" + python-version: ${{ matrix.python-version }} + cache: pip - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y graphviz python -m pip install --upgrade pip + pip install flake8 pytest pep8-naming if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + # на всякий случай — установить сам пакет pip install -e . - pip install pytest hypothesis flake8 pep8-naming - name: Lint with flake8 run: | - # если есть .flake8 в корне — flake8 прочитает правила из него - flake8 minitorch/ tests/ project/ + # stop the build if there are Python syntax errors or undefined names + flake8 --ignore "N801,E203,E266,E501,W503,W504,F812,F401,F841,E741,N803,N802,N806,E128,E302" minitorch/ tests/ project/ - - name: Run tests (Module 1 only) + - name: Test with pytest (Module 1) run: | echo "Module 1" - pytest -q -m task1_1 - pytest -q -m task1_2 - pytest -q -m task1_3 - pytest -q -m task1_4 + pytest tests -q -x -m task1_1 + pytest tests -q -x -m task1_2 + pytest tests -q -x -m task1_3 + pytest tests -q -x -m task1_4 From bd4c2030e12168b45ddaae90309cde5855a2e7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9F=D0=B0=D0=BB=D1=8B?= =?UTF-8?q?=D1=81=D0=B0=D0=B5=D0=B2?= Date: Sun, 19 Oct 2025 23:54:56 +0300 Subject: [PATCH 8/8] fix linker --- minitorch/scalar_functions.py | 2 +- project/run_scalar.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/minitorch/scalar_functions.py b/minitorch/scalar_functions.py index e9e1f5e1..92ee9ea2 100644 --- a/minitorch/scalar_functions.py +++ b/minitorch/scalar_functions.py @@ -105,7 +105,7 @@ class Mul(ScalarFunction): def forward(ctx: Context, a: float, b: float) -> float: ctx.save_for_backward(a, b) return float(operators.mul(a, b)) - + @staticmethod def backward(ctx: Context, d_output: float) -> Tuple[float, float]: a, b = ctx.saved_values diff --git a/project/run_scalar.py b/project/run_scalar.py index 2afef78b..26703cb0 100644 --- a/project/run_scalar.py +++ b/project/run_scalar.py @@ -111,4 +111,4 @@ def train(self, data, learning_rate, max_epochs=500, log_fn=default_log_fn): HIDDEN = 6 RATE = 0.5 data = minitorch.datasets["Split"](PTS) - ScalarTrain(HIDDEN).train(data, RATE, max_epochs=800) \ No newline at end of file + ScalarTrain(HIDDEN).train(data, RATE, max_epochs=800)