def _minimize(self): if self.return_errors: raise NotImplementedError( "Tensorflow minimizer does not yet support return errors") if self.use_hessian: # This optimizer can use the hessian information # Compute the inverse hessian at the guess inv_hess = self.lf.inverse_hessian(self.guess, omit_grads=tuple( self.fix.keys())) # Explicitly symmetrize the matrix inv_hess = fd.symmetrize_matrix(inv_hess) inv_hess = fd.np_to_tf(inv_hess) # Hessian cannot be used later in the inference self.use_hessian = False else: inv_hess = None kwargs = self.optimizer_kwargs x_guess = fd.np_to_tf(self._dict_to_array(self.guess)) return tfp.optimizer.bfgs_minimize( self.fun_and_grad, initial_position=x_guess, initial_inverse_hessian_estimate=inv_hess, **kwargs)
def __init__(self, data=None, batch_size=10, max_sigma=3, data_is_annotated=False, _skip_tf_init=False, _skip_bounds_computation=False, fit_params=None, **params): """ :param data: :param batch_size: used :param max_sigma: :param data_is_annotated: :param _skip_tf_init: :param _skip_bounds_computation: :param fit_params: List of parameters to fit :param params: New defaults """ self.defaults = dict() self.data = data if data is None: # We're calling the source without data. Set the batch_size here # since we can't pass it to set_data later self.batch_size = batch_size else: self._init_padding(batch_size, _skip_tf_init) self.data_tensor = fd.np_to_tf(self.data[self.column]) self.data_tensor = tf.reshape(self.data_tensor, (-1, self.batch_size, 1))
def gimme(self, fname, data_tensor=None, ptensor=None, bonus_arg=None, numpy_out=False): """Evaluate the model function fname with all required arguments :param fname: Name of the model function to compute :param bonus_arg: If fname takes a bonus argument, the data for it :param numpy_out: If True, return (tuple of) numpy arrays, otherwise (tuple of) tensors. :param data_tensor: Data tensor, columns as self.name_id If not given, use self.data (used in annotate) :param ptensor: Parameter tensor, columns as self.param_id If not give, use defaults dictionary (used in annotate) Before using gimme, you must use set_data to populate the internal caches. """ # TODO: make a clean way to keep track of i_batch or have it as input assert (bonus_arg is not None) == (fname in self.special_data_methods) if data_tensor is None: # We're in an annotate assert hasattr(self, 'data'), "You must set data first" else: # We're computing if not hasattr(self, 'name_id'): raise ValueError( "You must set_data first (and populate the tensor cache)") f = getattr(self, fname) if callable(f): args = [self._fetch(x, data_tensor) for x in self.f_dims[fname]] if bonus_arg is not None: args = [bonus_arg] + args kwargs = { pname: self._fetch_param(pname, ptensor) for pname in self.f_params[fname] } res = f(*args, **kwargs) else: if bonus_arg is None: n = len( self.data) if data_tensor is None else data_tensor.shape[0] x = tf.ones(n, dtype=fd.float_type()) else: x = tf.ones_like(bonus_arg, dtype=fd.float_type()) res = f * x if numpy_out: return fd.tf_to_np(res) return fd.np_to_tf(res)
def _fetch(self, x, data_tensor=None): """Return a tensor column from the original dataframe (self.data) :param x: column name :param data_tensor: Data tensor, columns as in self.name_id """ if x in self.ignore_columns(): raise RuntimeError( "Attempt to fetch %s, which is in ignore_columns" % x) if data_tensor is None: # We're inside annotate, just return the column return fd.np_to_tf(self.data[x].values) return data_tensor[:, self.name_id[x]]
def _fetch(self, x, data_tensor=None): """Return a tensor column from the original dataframe (self.data) :param x: column name :param data_tensor: Data tensor, columns as in self.name_id """ if data_tensor is None: # We're inside annotate, just return the column return fd.np_to_tf(self.data[x].values) col_id = tf.dtypes.cast(self.name_id.lookup(tf.constant(x)), fd.int_type()) # if i_batch is None: # return tf.reshape(self.data_tensor[:,:,col_id], [-1]) # else: return data_tensor[:, col_id]
def fun_and_grad(self, x): return fd.np_to_tf(super().fun_and_grad(x))
def bestfit(self, guess=None, fix=None, optimizer=tfp.optimizer.bfgs_minimize, llr_tolerance=0.1, get_lowlevel_result=False, use_hessian=True, autograph=True, **kwargs): """Return best-fit parameter tensor :param guess: Guess parameters: dict {param: guess} of guesses to use. :param fix: dict {param: value} of parameters to keep fixed during the minimzation. :param llr_tolerance: stop minimizer if change in -2 log likelihood becomes less than this (roughly: using guess to convert to relative tolerance threshold) :param get_lowlevel_result: Returns the full optimizer result instead of only the best fit parameters. Bool. :param use_hessian: Passes the hessian estimated at the guess to the optimizer. Bool. """ if fix is None: fix = dict() if guess is None: guess = dict() if not isinstance(guess, dict): raise ValueError("Guess must be a dictionary") guess = {**self.guess(), **guess, **fix} # Unfortunately we can only set the relative tolerance for the # objective; we'd like to set the absolute one. # Use the guess log likelihood to normalize; if llr_tolerance is not None: kwargs.setdefault('f_relative_tolerance', llr_tolerance/self.minus_ll(**guess)[0]) # Create a vector of guesses for the optimizer # Store as temp attributes so we can access them also in objective self._varnames = [k for k in self.param_names if k not in fix] self._guess_vect = fd.np_to_tf(np.array([ guess[k] for k in self._varnames])) self._fix = fix # Minimize multipliers to the guess, rather than the guess itself # This is a basic kind of standardization that helps make the gradient # vector reasonable. x_norm = tf.ones(len(self._varnames), dtype=fd.float_type()) if optimizer == tfp.optimizer.bfgs_minimize and use_hessian: # This optimizer can use the hessian information # Compute the inverse hessian at the guess inv_hess = self.inverse_hessian(guess, omit_grads=tuple(fix.keys())) # We use scaled values in the optimizer so also scale the # hessian. We need to multiply the hessian with the parameter # values. This is the inverse hessian so we divide. inv_hess /= tf.linalg.tensordot( self._guess_vect, self._guess_vect, axes=0) # Explicitly symmetrize the matrix inv_hess = fd.symmetrize_matrix(inv_hess) else: inv_hess = None self._autograph_objective = autograph res = optimizer(self.objective, x_norm, initial_inverse_hessian_estimate=inv_hess, **kwargs) if get_lowlevel_result: return res if res.failed: raise ValueError(f"Optimizer failure! Result: {res}") res = res.position * self._guess_vect res = {k: res[i].numpy() for i, k in enumerate(self._varnames)} return {**res, **fix}