def coordinate_descent(sigma, cost_fn, step_size=0.1, step_change=0.5, step_min=1e-5, tolerance=1e-2, max_iter=100, **kwargs): stepinfo, update_stepinfo = tc.create_stepinfo() stop_fit = lambda: (tc.error_non_decreasing( stepinfo, tolerance) or tc.max_iterations_reached(stepinfo, max_iter) or tc.less_than_equal(step_size, step_min)) this_sigma = sigma.copy() n_parameters = len(sigma) step_errors = np.empty([n_parameters, 2]) while not stop_fit(): for i in range(0, n_parameters): # Try shifting each parameter both negatively and positively # proportional to step_size, and save both the new # sigma vectors and resulting cost_fn outputs this_sigma[i] = sigma[i] + step_size step_errors[i, 0] = cost_fn(this_sigma) this_sigma[i] = sigma[i] - step_size step_errors[i, 1] = cost_fn(this_sigma) this_sigma[i] = sigma[i] # Get index tuple for the lowest error that resulted, # and keep the corresponding sigma vector for the next iteration i_param, j_sign = np.unravel_index(step_errors.argmin(), step_errors.shape) err = step_errors[i_param, j_sign] # If change was negative, try reducing step size. if err > stepinfo['err']: log.debug( "Error got worse, reducing step size from: %.06f to: " "%.06f", step_size, step_size * step_change) step_size *= step_change continue # If j is 1, shift was negative, otherwise it was 0 for positive. if j_sign == 1: sigma[i_param] = this_sigma[i_param] = sigma[i_param] - step_size else: sigma[i_param] = this_sigma[i_param] = sigma[i_param] + step_size update_stepinfo(err=err) if stepinfo['stepnum'] % 20 == 0: log.debug("sigma is now: %s", sigma) log.info("Final error: %.06f\n", stepinfo['err']) return sigma
def dummy_fitter(sigma, cost_fn, bounds=None, fixed=None): ''' This fitter does not actually take meaningful steps; it merely varies the first element of the sigma vector to be equal to the step number. It is intended purely for testing and example purposes so that you can see how to re-use termination conditions to write your own fitter. ''' # Define a stepinfo and termination condition function 'stop_fit' stepinfo, update_stepinfo = tc.create_stepinfo() stop_fit = lambda : (tc.error_non_decreasing(stepinfo, 1e-5) or tc.max_iterations_reached(stepinfo, 1000)) while not stop_fit(): sigma[0] = stepinfo['stepnum'] # Take a fake step err = cost_fn(sigma) update_stepinfo(err=err, sigma=sigma) return sigma
def coordinate_descent(sigma, cost_fn, step_size=0.1, step_change=0.5, step_min=1e-5, tolerance=1e-5, max_iter=100, bounds=None, **kwargs): if bounds is not None: bounds = list(zip(*bounds)) stepinfo, update_stepinfo = tc.create_stepinfo() stop_fit = lambda : (tc.error_non_decreasing(stepinfo, tolerance) or tc.max_iterations_reached(stepinfo, max_iter) or tc.less_than_equal(step_size, step_min)) this_sigma = sigma.copy() n_parameters = len(sigma) step_errors = np.empty([n_parameters, 2]) log.info("CD intializing: step_size=%.2f, tolerance=%e, max_iter=%d", step_size, tolerance, max_iter) this_steps = 0 while not stop_fit(): for i in range(0, n_parameters): if bounds is None: lower = np.NINF upper = np.inf else: lower = bounds[i][0] if bounds[i][0] is not None else np.NINF upper = bounds[i][1] if bounds[i][1] is not None else np.inf # Try shifting each parameter both negatively and positively # proportional to step_size, and save both the new # sigma vectors and resulting cost_fn outputs this_sigma[i] = sigma[i] + step_size if this_sigma[i] > upper: this_sigma[i] = upper step_errors[i, 0] = cost_fn(this_sigma) this_sigma[i] = sigma[i] - step_size if this_sigma[i] < lower: this_sigma[i] = lower step_errors[i, 1] = cost_fn(this_sigma) this_sigma[i] = sigma[i] # Get index tuple for the lowest error that resulted, # and keep the corresponding sigma vector for the next iteration i_param, j_sign = np.unravel_index( step_errors.argmin(), step_errors.shape ) err = step_errors[i_param, j_sign] # If change was negative, try reducing step size. if err >= stepinfo['err']: log.info("Error worse, reducing step size from %.06f to %.06f", step_size, step_size * step_change) step_size *= step_change this_steps = 0 continue else: this_steps += 1 if this_steps > 10: log.info("Increasing step size from %.06f to %.6f", step_size, step_size / np.sqrt(step_change)) this_steps = 0 step_size /= np.sqrt(step_change) # If j is 1, shift was negative, otherwise it was 0 for positive. if j_sign == 1: sigma[i_param] = this_sigma[i_param] = sigma[i_param] - step_size else: sigma[i_param] = this_sigma[i_param] = sigma[i_param] + step_size update_stepinfo(err=err) log.debug("step=%d", stepinfo["stepnum"]) if stepinfo['stepnum'] % 20 == 0: log.debug("sigma is now: %s", sigma) log.info("Final error: %.06f (step size %.06f)\n", stepinfo['err'], step_size) return sigma