Mercurial > lbo > hg > autodiff
changeset 1:4b54659483c0
gad: fix bug, Jacobians work well now
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Thu, 23 Dec 2021 00:47:27 +0100 |
parents | 4486b31d87da |
children | 3cd197157ea7 |
files | autodiff.py gad.py |
diffstat | 2 files changed, 55 insertions(+), 24 deletions(-) [+] |
line wrap: on
line diff
--- a/autodiff.py Wed Dec 22 22:47:49 2021 +0100 +++ b/autodiff.py Thu Dec 23 00:47:27 2021 +0100 @@ -1,3 +1,9 @@ +""" +Copyright (c) 2021 Lewin Bormann + +Simple backpropagation, stateful, naive approach. +""" + import numpy as np class Expression:
--- a/gad.py Wed Dec 22 22:47:49 2021 +0100 +++ b/gad.py Thu Dec 23 00:47:27 2021 +0100 @@ -1,38 +1,52 @@ +""" +Copyright (c) 2021 Lewin Bormann + +Reverse-mode automatic differentiation algorithm. +""" + import numpy as np class Expression: - def __init__(self, left, right): + def __init__(self, left, right, ade=None): self.l = left self.r = right - print(left, right) self.eval_l = None self.eval_r = None + + self.ade = ade or getattr(left, 'ade', None) or getattr(right, 'ade', None) def fw(self, v): pass def bw(self): pass + def _autoconv(self, v): + if isinstance(v, Expression): + return v + elif type(v) in (int, float): + return self.ade.const(v) + assert False, f'unknown type used in expression: {type(v)}' + def __add__(self, other): - return OpPlus(self, other) + return OpPlus(self, self._autoconv(other)) def __sub__(self, other): - return OpMinus(self, other) + return OpMinus(self, self._autoconv(other)) def __neg__(self, other): - return Num(name=self.name, id=self.id) + return OpMinus(self.ade.const(0), self._autoconv(other)) def __mul__(self, other): - return OpMult(self, other) + return OpMult(self, self._autoconv(other)) def __truediv__(self, other): - return OpDiv(self, other) + return OpDiv(self, self._autoconv(other)) def __pow__(self, other): - return OpPow(self, other) + return OpPow(self, self._autoconv(other)) class Num(Expression): - - def __init__(self, i, n): + def __init__(self, i, n, ade=None): self.i = i - self.g = np.zeros(n) + self.g = np.zeros((n, 1)) self.g[i] = 1 + self.ade = ade def fw(self, v): return v[self.i] @@ -40,15 +54,16 @@ def bw(self): return self.g -def Const(Expression): - def __init__(self, v): +class Const(Expression): + def __init__(self, n, v): self.v = v + self.n = n def fw(self, v): return self.v def bw(self): - return 0 + return np.zeros(self.n)[:,None] class OpPlus(Expression): def fw(self, v): @@ -81,12 +96,11 @@ return self.eval_l / self.eval_r def bw(self): - gl = self.l.bw()[:,None] - gr = self.r.bw()[:,None] - g = np.array([1/self.eval_r, -self.eval_l/self.eval_r**2]) + gl = self.l.bw() + gr = self.r.bw() + g = np.array([1/self.eval_r, -self.eval_l/self.eval_r**2])[:,None] # Jacobian.T x gradient J = np.hstack((gl, gr)) - print(J.shape, g.shape) return J @ g class OpPow(Expression): @@ -102,7 +116,7 @@ v = np.exp(self.eval_r * np.log(self.eval_l)) thisgrad = np.array([ self.eval_r/self.eval_l * v, - np.log(self.eval_l) * v]) + np.log(self.eval_l) * v])[:,None] return np.hstack((gl, gr)) @ thisgrad class UnaryExpression(Expression): @@ -144,7 +158,10 @@ self.n = n_variables def vars(self): - return [Num(i, self.n) for i in range(self.n)] + return [Num(i, self.n, ade=self) for i in range(self.n)] + + def const(self, v): + return Const(self.n, v) def eval(self, expr, vals): v = np.array(vals) @@ -156,7 +173,7 @@ value = self.eval(expr, at) if type(expr) in [list, np.ndarray]: # Calculate jacobian - return value, np.vstack([e.bw() for e in expr]) + return value, np.vstack([e.bw().T for e in expr]) return value, expr.bw() def __enter__(self): @@ -167,10 +184,18 @@ ade = ADE(3) [x,y,z] = ade.vars() +v = np.array([x,y,z]) f = x*y + y/z -g = sqrt(x**Const(2) + y**Const(2) + z**Const(2)) -print(ade.eval(f, [1,2,3])) +def complex_calculation(x,y,z): + a = x + y + b = x - z + c = a * b + for i in range(4): + c = c + a*b + return c -print(ade.grad([f, g], [1,2,3])) +print(ade.eval([f], [1,2,3])) + +print(ade.grad([complex_calculation(x,y,z)], [1,4,5]))