Scan, Shared Variables, and Root Finding #959
Replies: 2 comments 8 replies
-
The updates that are supposed to occur within a N.B. If you're wondering why a |
Beta Was this translation helpful? Give feedback.
-
I saw that example, and it left me with more questions than answer because I was indeed wondering why the I changed the returns in the inner function to a Here is my new inner function: def newton_step(X, Fx, Jx, step_size, tol):
new_X = X - step_size * at.linalg.solve(Jx, Fx)
return {X:new_X}, scan_until(at.sqr(Fx).sum() < tol) The I wrote up a minimum example to reproduce all the steps I'm taking, in case something about how I am parsing and setting up the matrices is wrong. from sympy.abc import x, y, a, b, c, d, e, f
from sympy.printing.aesaracode import aesara_function, aesara_code
import aesara
import aesara.tensor as at
from aesara.scan.utils import until as scan_until
import numpy as np
# Sympy equations
cache_dict = {}
eq_1 = a * x ** 2 - b * y - c
eq_2 = d * x - e * y ** 2 + f
system = [eq_1, eq_2]
# Print sympy to aesara
cache_dict = {}
params = [aesara_code(param, cache=cache_dict) for param in [a, b, c, d, e, f]]
variables = [aesara_code(var, cache=cache_dict) for var in [x, y]]
vars_shared = aesara.shared(np.ones(2), name='variables')
shared_replace_dict = dict(zip(variables, vars_shared))
# Inject shared variables into generated code
aesara_system = at.stack([aesara_code(eq, cache=cache_dict) for eq in system])
shared_system = aesara.clone_replace(aesara_system, replace=shared_replace_dict)
jacobian = at.stack([[at.grad(eq, x) for x in variables] for eq in aesara_system])
jacobian_shared = aesara.clone_replace(jacobian, replace=shared_replace_dict)
# "by hand" approach, this works fine by looping over f_newton_1 and using get_value/set_value
# vars_updated = vars_shared - at.linalg.solve(jacobian_shared, shared_system)
# updates = [(vars_shared, vars_updated)]
# f_newton_1= aesara.function(params, shared_system, updates=updates)
# param_values = np.ones(6)
# for _ in range(50):
# resid = f_ss(*param_values)
# if np.linalg.norm(resid, 1) < 1e-16:
# break
## Scan approach, doesn't work
step_size = at.dscalar('step_size')
max_iter = at.iscalar('max_iter')
tol = at.dscalar('tol')
def newton_step(X, Fx, Jx, step_size, tol):
new_X = X - step_size * at.linalg.solve(Jx, Fx)
return {X:new_X}, scan_until(at.sqr(Fx).sum() < tol)
result, updates = aesara.scan(newton_step,
non_sequences=[vars_shared, shared_system, jacobian_shared,
step_size, tol],
n_steps=max_iter,
strict=True)
# f_newton_2 diverges to infinity
f_newton_2 = aesara.function(params + [step_size, max_iter, tol], shared_system, updates=updates) |
Beta Was this translation helpful? Give feedback.
-
Hi everyone,
I am trying to implement Newton's method to find the roots of a nonlinear system of equations. I load the system from Sympy expressions, so I am trying to avoid having to pick those apart completely if possible, but we'll see.
aesara_ss_system
is a vector of non-linear equations built withsympy.printing.aesaracode.aesara_code
, which has model parameters to be estimated (aesara_params
), and variables to solve the system for given parameters (aesara_vars_ss
)I need the solutions x* (i called it
steady_state_values
in the code), such that f(x*) = 0 in order to get a first-order linear approximation of the system in the next step, so I want to update the equation matrices in-place. Given that, I thought using Shared Variables would be the way to go. I wrote the algorithm like this:f_ss
works exactly as I want, and I can iterate on a set of fixed parameter values (aesara_params
, which will eventually be estimated with PyMC) to get my x*. The problem is I have to compile this function, which means I can't get end-to-end gradients for the NUTS sampler down the line. I thought it might be possible to instead do keep the same idea with a shared variable but do the iteration with a scan, but I can't seem to get that to work. I tried:This is similar to what I came up with for another optimization algorithm, but it doesn't work because
steady_state_values
never updates, soshared_ss_system
andjacobian_shared
also stay put. I tried puttingsteady_state_value
as a non_sequence, trying to updateshared_ss_system
andjacobian_shared
in thenewton_step
function usingclone_replace
, but nothing seems to propagate the updates. In the optimizer I got working I was able to explicitly write down an update rule for each component of the system inside the step function, so I can be sure everything is updating. Here I am just crossing my fingers and praying the information goes where it needs to. It's working about as well as you'd expect.I'm also surprised that the updates dictionary comes back empty. I thought when a shared variable is passed into a Scan you are meant to get back an update. Any ideas how to get something like this to work? Am I on the right track with a SharedVariable, or is that unnecessary?
Beta Was this translation helpful? Give feedback.
All reactions