P-unit_model/Fitter.py

345 lines
14 KiB
Python

from models.LIFACnoise import LifacNoiseModel
from stimuli.SinusoidalStepStimulus import SinusoidalStepStimulus
from CellData import CellData, icelldata_of_dir
from FiCurve import FICurve
from AdaptionCurrent import Adaption
import helperFunctions as hF
import functions as fu
import numpy as np
from scipy.optimize import minimize
import time
import os
def main():
run_with_real_data()
def run_with_real_data():
count = 0
for cell_data in icelldata_of_dir("./data/"):
count += 1
if count <= 3:
continue
print("cell:", cell_data.get_data_path())
trace = cell_data.get_base_traces(trace_type=cell_data.V1)
if len(trace) == 0:
print("NO V1 TRACE FOUND")
continue
results_path = "results/" + os.path.split(cell_data.get_data_path())[-1] + "/"
print("results at:", results_path)
start_time = time.time()
fitter = Fitter()
fmin, parameters = fitter.fit_model_to_data(cell_data)
print(fmin)
print(parameters)
end_time = time.time()
if not os.path.exists(results_path):
os.makedirs(results_path)
with open(results_path + "fit_parameters.txt", "w") as file:
file.writelines([str(parameters)])
results_path += "fit_routine_2_"
print('Fitting of cell took function took {:.3f} s'.format((end_time - start_time)))
print_comparision_cell_model(cell_data, parameters, plot=True, savepath=results_path)
pass
def print_comparision_cell_model(cell_data, parameters, plot=False, savepath=None):
res_model = LifacNoiseModel(parameters)
m_bf, m_vs, m_sc = res_model.calculate_baseline_markers(cell_data.get_eod_frequency())
m_f_values, m_f_slope = res_model.calculate_fi_markers(cell_data.get_fi_contrasts(), cell_data.get_eod_frequency())
c_bf = cell_data.get_base_frequency()
c_vs = cell_data.get_vector_strength()
c_sc = cell_data.get_serial_correlation(1)
fi_curve = FICurve(cell_data)
c_f_slope = fi_curve.get_f_infinity_slope()
c_f_values = fi_curve.f_infinities
print("bf: cell - {:.2f} vs model {:.2f}".format(c_bf, m_bf))
print("vs: cell - {:.2f} vs model {:.2f}".format(c_vs, m_vs))
print("sc: cell - {:.2f} vs model {:.2f}".format(c_sc[0], m_sc[0]))
print("f_slope: cell - {:.2f} vs model {:.2f}".format(c_f_slope, m_f_slope))
print("f values:\n cell -", c_f_values, "\n model -", m_f_values)
if plot:
f_b, f_zero, f_inf = res_model.calculate_fi_curve(cell_data.get_fi_contrasts(), cell_data.get_eod_frequency())
fi_curve.plot_fi_curve(savepath=savepath, comp_f_baselines=f_b, comp_f_zeros=f_zero, comp_f_infs=f_inf)
class Fitter:
def __init__(self, params=None):
if params is None:
self.base_model = LifacNoiseModel({"step_size": 0.00005})
else:
self.base_model = LifacNoiseModel(params)
if "step_size" not in params:
self.base_model.set_variable("step_size", 0.00005)
#
self.fi_contrasts = []
self.eod_freq = 0
self.sc_max_lag = 1
# values to be replicated:
self.baseline_freq = 0
self.vector_strength = -1
self.serial_correlation = []
self.f_inf_values = []
self.f_inf_slope = 0
self.f_zero_values = []
self.f_zero_slope = 0
self.f_zero_fit = []
self.tau_a = 0
self.delta_a = 0
# counts how often the cost_function was called
self.counter = 0
def fit_model_to_data(self, data: CellData):
self.eod_freq = data.get_eod_frequency()
self.baseline_freq = data.get_base_frequency()
self.vector_strength = data.get_vector_strength()
self.serial_correlation = data.get_serial_correlation(self.sc_max_lag)
fi_curve = FICurve(data, contrast=True)
self.fi_contrasts = fi_curve.stimulus_value
self.f_inf_values = fi_curve.f_infinities
self.f_inf_slope = fi_curve.get_f_infinity_slope()
self.f_zero_values = fi_curve.f_zeros
self.f_zero_fit = fi_curve.boltzmann_fit_vars
self.f_zero_slope = fi_curve.get_fi_curve_slope_of_straight()
self.delta_a = (self.f_zero_slope / self.f_inf_slope) / 1000
adaption = Adaption(data, fi_curve)
self.tau_a = adaption.get_tau_real()
return self.fit_routine_2(data)
# return self.fit_model(fit_adaption=False)
def fit_routine_1(self, cell_data=None):
# errors: [error_bf, error_vs, error_sc, error_f_inf, error_f_inf_slope, error_f_zero, error_f_zero_slope]
self.counter = 0
# fit only v_offset, mem_tau, noise_strength, input_scaling
x0 = np.array([0.02, 0.03, 70])
initial_simplex = create_init_simples(x0, search_scale=2)
error_weights = (1, 1, 1, 1, 1, 0, 0)
fmin_step1 = minimize(fun=self.cost_function_with_fixed_adaption, args=(self.tau_a, self.delta_a, error_weights), x0=x0, method="Nelder-Mead",
options={"initial_simplex": initial_simplex})
res_parameters_step1 = self.base_model.get_parameters()
if cell_data is not None:
print("##### After step 1: (fixed adaption)")
print_comparision_cell_model(cell_data, res_parameters_step1)
self.counter = 0
x0 = np.array([res_parameters_step1["mem_tau"], res_parameters_step1["noise_strength"],
res_parameters_step1["input_scaling"], res_parameters_step1["tau_a"],
res_parameters_step1["delta_a"]])
initial_simplex = create_init_simples(x0, search_scale=2)
error_weights = (1, 1, 1, 1, 1, 2, 4)
fmin_step2 = minimize(fun=self.cost_function_all, args=(error_weights), x0=x0, method="Nelder-Mead",
options={"initial_simplex": initial_simplex})
res_parameters_step2 = self.base_model.get_parameters()
if cell_data is not None:
print("##### After step 2: (Everything)")
# print_comparision_cell_model(cell_data, res_parameters_step2)
return fmin_step2, res_parameters_step2
def fit_routine_2(self, cell_data=None):
# errors: [error_bf, error_vs, error_sc, error_f_inf, error_f_inf_slope, error_f_zero, error_f_zero_slope]
self.counter = 0
# fit only v_offset, mem_tau, noise_strength, input_scaling
x0 = np.array([0.02, 0.03, 70])
initial_simplex = create_init_simples(x0, search_scale=2)
error_weights = (1, 1, 5, 1, 2, 0, 0)
fmin = minimize(fun=self.cost_function_with_fixed_adaption,
args=(self.tau_a, self.delta_a, error_weights), x0=x0, method="Nelder-Mead",
options={"initial_simplex": initial_simplex})
res_parameters = self.base_model.get_parameters()
return fmin, res_parameters
def fit_model(self, x0=None, initial_simplex=None, fit_adaption=False):
self.counter = 0
if fit_adaption:
if x0 is None:
x0 = np.array([0.02, 0.03, 70, self.tau_a, self.delta_a])
if initial_simplex is None:
initial_simplex = create_init_simples(x0)
fmin = minimize(fun=self.cost_function_all, x0=x0, method="Nelder-Mead", options={"initial_simplex": initial_simplex})
else:
if x0 is None:
x0 = np.array([0.02, 0.03, 70])
if initial_simplex is None:
initial_simplex = create_init_simples(x0)
fmin = minimize(fun=self.cost_function_with_fixed_adaption, x0=x0, args=(self.tau_a, self.delta_a), method="Nelder-Mead", options={"initial_simplex": initial_simplex})
return fmin, self.base_model.get_parameters()
def cost_function_all(self, X, error_weights=None):
self.base_model.set_variable("mem_tau", X[0])
self.base_model.set_variable("noise_strength", X[1])
self.base_model.set_variable("input_scaling", X[2])
self.base_model.set_variable("tau_a", X[3])
self.base_model.set_variable("delta_a", X[4])
base_stimulus = SinusoidalStepStimulus(self.eod_freq, 0)
# find right v-offset
test_model = self.base_model.get_model_copy()
test_model.set_variable("noise_strength", 0)
v_offset = test_model.find_v_offset(self.baseline_freq, base_stimulus)
self.base_model.set_variable("v_offset", v_offset)
# [error_bf, error_vs, error_sc, error_f_inf, error_f_inf_slope, error_f_zero, error_f_zero_slope]
error_list = self.calculate_errors(error_weights)
return sum(error_list)
def cost_function_only_adaption_and_v_offset(self, X, error_weights=None):
self.base_model.set_variable("tau_a", X[0])
self.base_model.set_variable("delta_a", X[1])
base_stimulus = SinusoidalStepStimulus(self.eod_freq, 0)
# find right v-offset
test_model = self.base_model.get_model_copy()
test_model.set_variable("noise_strength", 0)
v_offset = test_model.find_v_offset(self.baseline_freq, base_stimulus)
self.base_model.set_variable("v_offset", v_offset)
# [error_bf, error_vs, error_sc, error_f_inf, error_f_inf_slope, error_f_zero, error_f_zero_slope]
error_list = self.calculate_errors(error_weights)
return sum(error_list)
def cost_function_only_adaption(self, X, error_weights=None):
self.base_model.set_variable("tau_a", X[0])
self.base_model.set_variable("delta_a", X[1])
error_list = self.calculate_errors(error_weights)
return sum(error_list)
def cost_function_with_fixed_adaption(self, X, tau_a, delta_a, error_weights=None):
# set model parameters:
model = self.base_model
model.set_variable("mem_tau", X[0])
model.set_variable("noise_strength", X[1])
model.set_variable("input_scaling", X[2])
model.set_variable("tau_a", tau_a)
model.set_variable("delta_a", delta_a)
base_stimulus = SinusoidalStepStimulus(self.eod_freq, 0)
# find right v-offset
test_model = model.get_model_copy()
test_model.set_variable("noise_strength", 0)
v_offset = test_model.find_v_offset(self.baseline_freq, base_stimulus)
model.set_variable("v_offset", v_offset)
error_list = self.calculate_errors(error_weights)
return sum(error_list)
def calculate_errors(self, error_weights=None):
baseline_freq, vector_strength, serial_correlation = self.base_model.calculate_baseline_markers(self.eod_freq,
self.sc_max_lag)
# print("baseline features calculated!")
# f_infinities, f_infinities_slope = self.base_model.calculate_fi_markers(self.fi_contrasts, self.eod_freq)
f_baselines, f_zeros, f_infinities = self.base_model.calculate_fi_curve(self.fi_contrasts, self.eod_freq)
f_infinities_fit = hF.fit_clipped_line(self.fi_contrasts, f_infinities)
f_infinities_slope = f_infinities_fit[0]
f_zeros_fit = hF.fit_boltzmann(self.fi_contrasts, f_zeros)
f_zero_slope = fu.full_boltzmann_straight_slope(f_zeros_fit[0], f_zeros_fit[1], f_zeros_fit[2], f_zeros_fit[3])
# print("fi-curve features calculated!")
# calculate errors with reference values
error_bf = abs((baseline_freq - self.baseline_freq) / self.baseline_freq)
error_vs = abs((vector_strength - self.vector_strength) / self.vector_strength)
error_sc = abs((serial_correlation[0] - self.serial_correlation[0]) / self.serial_correlation[0])
error_f_inf_slope = abs((f_infinities_slope - self.f_inf_slope) / self.f_inf_slope) * 4
error_f_inf = calculate_f_values_error(f_infinities, self.f_inf_values) * .5
error_f_zero_slope = abs((f_zero_slope - self.f_zero_slope) / self.f_zero_slope)
error_f_zero = calculate_f_values_error(f_zeros, self.f_zero_values)
error_list = [error_bf, error_vs, error_sc, error_f_inf, error_f_inf_slope, error_f_zero, error_f_zero_slope]
if error_weights is not None and len(error_weights) == len(error_list):
for i in range(len(error_weights)):
error_list[i] = error_list[i] * error_weights[i]
error = sum(error_list)
self.counter += 1
if self.counter % 200 == 0:
print("\nCost function run times: {:}\n".format(self.counter),
"Total weighted error: {:.4f}\n".format(error),
"Baseline frequency - expected: {:.0f}, current: {:.0f}, error: {:.3f}\n".format(
self.baseline_freq, baseline_freq, error_bf),
"Vector strength - expected: {:.2f}, current: {:.2f}, error: {:.3f}\n".format(
self.vector_strength, vector_strength, error_vs),
"Serial correlation - expected: {:.2f}, current: {:.2f}, error: {:.3f}\n".format(
self.serial_correlation[0], serial_correlation[0], error_sc),
"f-infinity slope - expected: {:.0f}, current: {:.0f}, error: {:.3f}\n".format(
self.f_inf_slope, f_infinities_slope, error_f_inf_slope),
"f-infinity values:\nexpected:", np.around(self.f_inf_values), "\ncurrent: ", np.around(f_infinities),
"\nerror: {:.3f}\n".format(error_f_inf),
"f-zero slope - expected: {:.0f}, current: {:.0f}, error: {:.3f}\n".format(
self.f_zero_slope, f_zero_slope, error_f_zero_slope),
"f-zero values:\nexpected:", np.around(self.f_zero_values), "\ncurrent: ", np.around(f_zeros),
"\nerror: {:.3f}".format(error_f_zero))
return error_list
def calculate_f_values_error(fit, reference):
error = 0
for i in range(len(reference)):
# TODO ??? add a constant to f_inf to allow for small differences in small values
# example: 1 vs 3 would result in way smaller error.
constant = 0
error += abs((fit[i] - reference[i]) / (reference[i] + constant))
norm_error = error / len(reference)
return norm_error
def create_init_simples(x0, search_scale=3.):
dim = len(x0)
simplex = [[x0[0]/search_scale], [x0[0]*search_scale]]
for i in range(1, dim, 1):
for vertex in simplex:
vertex.append(x0[i]*search_scale)
new_vertex = list(x0[:i])
new_vertex.append(x0[i]/search_scale)
simplex.append(new_vertex)
return simplex
if __name__ == '__main__':
main()