Loss functions

Fitting with an alternative loss function

Fitting methods can be modified by changing the loss function or by changing the algorithm used to optimize the loss function. The default loss function is likelihood (via LossLikelihood). Squared error (via LossSquaredError) and BIC (via LossBIC) are also available.

As an example, to fit the “Simple example” from the quickstart guide, do:

fit_adjust_model(samp, model_fit,
                 method="differential_evolution",
                 lossfunction=LossSquaredError)

Custom loss functions may be defined by extending LossFunction. The loss function must be defined and, given a model, returns the goodness of fit to the sample, accessible under self.sample. The setup function may optionally be defined, which is run once and may be used, for example, to do computations on the sample don’t need to be run at each evaluation.

Fitting using mean RT and P(correct)

Here is an example of how to define a loss function as the sum of two terms: the mean response time of correct trials, and the probability of choosing the correct target. While unprincipled, it is a simple example that in practice gives similar fits to the analytical expression in Roitman and Shadlen (2002):

import numpy as np
from pyddm import LossFunction
class LossByMeans(LossFunction):
    name = "Mean RT and accuracy"
    def setup(self, dt, T_dur, **kwargs):
        self.dt = dt
        self.T_dur = T_dur
    def loss(self, model):
        sols = self.cache_by_conditions(model)
        MSE = 0
        for comb in self.sample.condition_combinations(required_conditions=self.required_conditions):
            c = frozenset(comb.items())
            s = self.sample.subset(**comb)
            MSE += (sols[c].prob("correct") - s.prob("correct"))**2
            if sols[c].prob("correct") > 0:
                MSE += (sols[c].mean_decision_time() - np.mean(list(s)))**2
        return MSE

Fitting with undecided trials

In addition to correct and incorrect trials, some trials may go beyond the time allowed for a decision. The effect of these trials is usually minor due to the design of task paradigms, but PyDDM is capable of using these values within its fitting procedures.

Currently, the functions which import Sample objects from numpy arrays do not support undecided trials; thus, to include undecided trials in a sample, they must be passed directly to the Sample constructor in a more complicated form.

To construct a sample with undecided trials, first create a Numpy array of correct RTs and incorrect RTs in units of seconds, and count the number of undecided trials. Then, for each task conditions, create a tuple containing three elements. The first element should be a Numpy array with the task condition value for each associated correct RT, the second should be the same but for error trials, and the final element should be a Numpy array in no particular order with a number of elements equal to the undecided trials, with one corresponding to each undecided trial.

Consider the following example with “reward” as the task condition. We suppose there is one correct trial with a reward of 3 and an RT of 0.3s, one error with a reward of 2 and an RT of 0.5s, and two undecided trials with rewards of 1 and 2:

sample = Sample(np.asarray([0.3]), np.asarray([0.5]), 2,
                reward=(np.asarray([3]), np.asarray([2]), np.asarray([1, 2])))

A sample created using this method can be used the same way as one created using from_numpy_array() or from_pandas_dataframe().