Exemple #1
0
def _demo_fusion():
    len_alp = 4
    evidence_names = ['LM', 'ERP', 'FRP']
    num_series = 4
    num_inquiries = 10

    conjugator = EvidenceFusion(evidence_names, len_dist=len_alp)

    print('Random Series!')
    for idx_ep in range(num_series):
        prior = np.abs(np.random.randn(len_alp))
        prior = prior / np.sum(prior)
        conjugator.update_and_fuse({'LM': prior})
        for idx in range(num_inquiries):
            # Generate random inquiries
            evidence_erp = 10 * np.abs(np.random.randn(len_alp))
            evidence_frp = 10 * np.abs(np.random.randn(len_alp))
            conjugator.update_and_fuse(
                {'ERP': evidence_erp, 'FRP': evidence_frp})
        print('Series: {}'.format(idx_ep))
        print(conjugator.evidence_history['ERP'])
        print(conjugator.evidence_history['FRP'])
        print(conjugator.evidence_history['LM'])
        print('Posterior:{}'.format(conjugator.likelihood))

        # Reset the conjugator before starting a new series for clear history
        conjugator.reset_history()
Exemple #2
0
def _demo_decision_maker():
    alp = ['T', 'H', 'I', 'S', 'I', 'S', 'D', 'E', 'M', 'O']
    len_alp = len(alp)
    evidence_names = ['LM', 'ERP', 'FRP']
    num_series = 10

    conjugator = EvidenceFusion(evidence_names, len_dist=len_alp)
    decision_maker = DecisionMaker(
        min_num_inq=1,
        max_num_inq=10,
        state='',
        alphabet=alp)

    for idx_series in range(num_series):

        while True:
            # Generate random inquiries
            evidence_erp = np.abs(np.random.randn(len_alp))
            evidence_erp[idx_series] += 1
            evidence_frp = np.abs(np.random.randn(len_alp))
            evidence_frp[idx_series] += 3

            p = conjugator.update_and_fuse(
                {'ERP': evidence_erp, 'FRP': evidence_frp})

            d, arg = decision_maker.decide(p)
            if d:
                break
        # Reset the conjugator before starting a new series for clear history
        conjugator.reset_history()

    print('State:{}'.format(decision_maker.state))
    print('Displayed State: {}'.format(decision_maker.displayed_state))
Exemple #3
0
class TestDecisionMaker(unittest.TestCase):
    """Test for decision maker class """
    def setUp(self):
        """Set up decision maker object for testing """
        self.decision_maker = DecisionMaker(
            1,
            3,
            state='',
            alphabet=list(string.ascii_uppercase) + ['<'] + [SPACE_CHAR],
            is_txt_stim=True,
            stimuli_timing=[1, .2],
            seq_constants=None)

        self.evidence_fusion = EvidenceFusion(list_name_evidence=['A', 'B'],
                                              len_dist=2)

    def tearDown(self):
        """Reset decision maker and evidence fusion at the
        end of each test. """
        self.decision_maker.reset()
        self.evidence_fusion.reset_history()

    def test_evidence_fusion_init(self):
        self.assertEqual(self.evidence_fusion.evidence_history, {
            'A': [],
            'B': []
        })
        self.assertEqual(self.evidence_fusion.likelihood[0], [0.5])
        self.assertEqual(self.evidence_fusion.likelihood[1], [0.5])

    def test_reset_history(self):
        self.evidence_fusion.reset_history()
        self.assertEqual(self.evidence_fusion.evidence_history, {
            'A': [],
            'B': []
        })
        self.assertEqual(self.evidence_fusion.likelihood[0], [0.5])
        self.assertEqual(self.evidence_fusion.likelihood[1], [0.5])

    def test_update_and_fuse_with_float_evidence(self):
        dict_evidence = {'A': [0.5], 'B': [0.5]}
        self.evidence_fusion.update_and_fuse(dict_evidence)
        self.assertEqual(self.evidence_fusion.evidence_history['A'][0],
                         dict_evidence['A'])
        self.assertEqual(self.evidence_fusion.evidence_history['B'][0],
                         dict_evidence['B'])
        self.assertEqual(self.evidence_fusion.likelihood[0], [[0.5]])
        self.assertEqual(self.evidence_fusion.likelihood[1], [[0.5]])

    def test_update_and_fuse_with_inf_evidence(self):
        dict_evidence = {'A': [0.5], 'B': [math.inf]}
        self.evidence_fusion.update_and_fuse(dict_evidence)
        self.assertEqual(self.evidence_fusion.evidence_history['A'][0],
                         dict_evidence['A'])
        self.assertEqual(self.evidence_fusion.evidence_history['B'][0],
                         dict_evidence['B'])
        self.assertEqual(self.evidence_fusion.likelihood[0], [1.0])
        self.assertEqual(self.evidence_fusion.likelihood[1], [0.0])

    def test_save_history(self):
        history = self.evidence_fusion.save_history()
        self.assertEqual(0, history)

    def test_decision_maker_init(self):
        """Test initialization"""
        self.assertEqual(self.decision_maker.min_num_seq, 1)
        self.assertEqual(self.decision_maker.max_num_seq, 3)
        self.assertEqual(self.decision_maker.state, '')
        self.assertEqual(self.decision_maker.displayed_state, '')

    def test_decide_without_commit(self):
        """
        Test decide method with case of no commit
        using a fake probability distribution
        """
        probability_distribution = np.ones(len(
            self.decision_maker.alphabet)) / 8
        decision, chosen_stimuli = self.decision_maker.decide(
            probability_distribution)
        self.assertTrue(
            np.all(self.decision_maker.list_epoch[-1]['list_distribution'][-1]
                   == probability_distribution))
        self.assertFalse(decision)
        self.decision_maker.do_epoch()
        self.assertEqual(self.decision_maker.sequence_counter, 0)

    def test_decide_with_commit(self):
        """Test decide method with case of commit"""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        self.decision_maker.sequence_counter = self.decision_maker.min_num_seq
        decision, chosen_stimuli = self.decision_maker.decide(
            probability_distribution)
        self.assertTrue(decision)
        self.assertEqual(chosen_stimuli, None)

    def test_update_with_letter(self):
        """Test update method with letter being the new state"""
        old_displayed_state = self.decision_maker.displayed_state
        old_state = self.decision_maker.state
        new_state = 'E'
        self.decision_maker.update(state=new_state)
        self.assertEqual(self.decision_maker.state, old_state + new_state)
        self.assertEqual(self.decision_maker.displayed_state,
                         old_displayed_state + 'E')

    def test_update_with_backspace(self):
        """Test update method with backspace being the new state"""
        old_displayed_state = self.decision_maker.displayed_state
        old_state = self.decision_maker.state
        new_state = '<'
        self.decision_maker.update(state=new_state)
        self.assertEqual(self.decision_maker.state, old_state + new_state)
        self.assertEqual(self.decision_maker.displayed_state,
                         old_displayed_state[0:-1])
        self.assertLess(len(self.decision_maker.displayed_state),
                        len(self.decision_maker.state))

    def test_reset(self):
        """Test reset of decision maker state"""
        self.decision_maker.reset()
        self.assertEqual(self.decision_maker.state, '')
        self.assertEqual(self.decision_maker.displayed_state, '')
        self.assertEqual(self.decision_maker.time, 0)
        self.assertEqual(self.decision_maker.sequence_counter, 0)

    def test_form_display_state(self):
        """Test form display state method with a dummy state"""
        self.decision_maker.update(state='ABC<.E')
        self.decision_maker.form_display_state(self.decision_maker.state)
        self.assertEqual(self.decision_maker.displayed_state, 'ABE')
        self.decision_maker.reset()

    def test_do_epoch(self):
        """Test do_epoch method"""
        probability_distribution = np.ones(len(
            self.decision_maker.alphabet)) / 8
        decision, chosen_stimuli = self.decision_maker.decide(
            probability_distribution)
        self.decision_maker.do_epoch()
        self.assertEqual(self.decision_maker.sequence_counter, 0)

    def test_decide_state_update(self):
        """Tests decide state update method"""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        self.decision_maker.list_epoch[-1]['list_distribution'].append(
            probability_distribution)
        decision = self.decision_maker.decide_state_update()
        expected = 'A'  # expect to commit to first letter in sequence, due to uniform probability
        self.assertEqual(decision, 'A')

    def test_schedule_sequence(self):
        """Test sequence scheduling. Should return new stimuli list, at random."""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        old_counter = self.decision_maker.sequence_counter
        self.decision_maker.list_epoch[-1]['list_distribution'].append(
            probability_distribution)
        stimuli = self.decision_maker.schedule_sequence()
        self.assertEqual(self.decision_maker.state, '.')
        self.assertEqual(stimuli[0],
                         self.decision_maker.list_epoch[-1]['list_sti'][-1])
        self.assertLess(old_counter, self.decision_maker.sequence_counter)

    def test_prepare_stimuli(self):
        """Test that stimuli are prepared as expected"""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        self.decision_maker.list_epoch[-1]['list_distribution'].append(
            probability_distribution)
        stimuli = self.decision_maker.prepare_stimuli()
        self.assertEqual(11, len(stimuli[0][0]))
        for i in range(1, len(stimuli[0][0])):
            self.assertIn(stimuli[0][0][i], self.decision_maker.alphabet)
        self.assertEqual(stimuli[1][0][0:2],
                         self.decision_maker.stimuli_timing)
Exemple #4
0
class CopyPhraseWrapper:
    """Basic copy phrase task duty cycle wrapper.

    Given the phrases once operate() is called performs the task.
    Attr:
        min_num_seq: The minimum number of sequences to be displayed
        max_num_seq: The maximum number of sequences to be displayed
        model(pipeline): model trained using a calibration session of the
            same user.
        fs(int): sampling frequency
        k(int): down sampling rate
        alp(list[str]): symbol set of the task
        task_list(list[tuple(str,str)]): list[(phrases, initial_states)] for
            the copy phrase task
        is_txt_stim: Whether or not the stimuli are text objects
        conjugator(EvidenceFusion): fuses evidences in the task
        decision_maker(DecisionMaker): mastermind of the task
        mode(str): mode of thet task (should be copy phrase)
        d(binary): decision flag
        sti(list(tuple)): stimuli for the display
        decision_threshold: Minimum likelihood value required for a decision
        backspace_prob(float): default language model probability for the
            backspace character.
        backspace_always_shown(bool): whether or not the backspace should
            always be presented.
    """
    def __init__(self,
                 min_num_seq,
                 max_num_seq,
                 signal_model=None,
                 fs=300,
                 k=2,
                 alp=None,
                 evidence_names=['LM', 'ERP'],
                 task_list=[('I_LOVE_COOKIES', 'I_LOVE_')],
                 lmodel=None,
                 is_txt_stim=True,
                 device_name='LSL',
                 device_channels=None,
                 stimuli_timing=[1, .2],
                 decision_threshold=0.8,
                 backspace_prob=0.05,
                 backspace_always_shown=False,
                 filter_high=45,
                 filter_low=2,
                 filter_order=2,
                 notch_filter_frequency=60):

        self.conjugator = EvidenceFusion(evidence_names, len_dist=len(alp))

        seq_constants = []
        if backspace_always_shown and BACKSPACE_CHAR in alp:
            seq_constants.append(BACKSPACE_CHAR)
        self.decision_maker = DecisionMaker(
            min_num_seq,
            max_num_seq,
            decision_threshold=decision_threshold,
            state=task_list[0][1],
            alphabet=alp,
            is_txt_stim=is_txt_stim,
            stimuli_timing=stimuli_timing,
            seq_constants=seq_constants)
        self.alp = alp
        # non-letter target labels include the fixation cross and calibration.
        self.nonletters = ['+', 'PLUS', 'calibration_trigger']
        self.valid_targets = set(self.alp)

        self.signal_model = signal_model
        self.sampling_rate = fs
        self.downsample_rate = k
        self.filter_high = filter_high
        self.filter_low = filter_low
        self.filter_order = filter_order
        self.notch_filter_frequency = notch_filter_frequency

        self.mode = 'copy_phrase'
        self.task_list = task_list
        self.lmodel = lmodel
        self.channel_map = analysis_channels(device_channels, device_name)
        self.backspace_prob = backspace_prob

    def evaluate_sequence(self, raw_data, triggers, target_info,
                          window_length):
        """Once data is collected, infers meaning from the data.

        Args:
            raw_data(ndarray[float]): C x L eeg data where C is number of
                channels and L is the signal length
            triggers(list[tuple(str,float)]): triggers e.g. ('A', 1)
                as letter and flash time for the letter
            target_info(list[str]): target information about the stimuli
            window_length(int): The length of the time between stimuli presentation
        """
        letters, times, target_info = self.letter_info(triggers, target_info)

        # Remove 60hz noise with a notch filter
        notch_filter_data = notch.notch_filter(
            raw_data,
            self.sampling_rate,
            frequency_to_remove=self.notch_filter_frequency)

        # bandpass filter from 2-45hz
        filtered_data = bandpass.butter_bandpass_filter(
            notch_filter_data,
            self.filter_low,
            self.filter_high,
            self.sampling_rate,
            order=self.filter_order)

        # downsample
        data = downsample.downsample(filtered_data,
                                     factor=self.downsample_rate)
        x, _, _, _ = trial_reshaper(target_info,
                                    times,
                                    data,
                                    fs=self.sampling_rate,
                                    k=self.downsample_rate,
                                    mode=self.mode,
                                    channel_map=self.channel_map,
                                    trial_length=window_length)

        lik_r = inference(x, letters, self.signal_model, self.alp)
        prob = self.conjugator.update_and_fuse({'ERP': lik_r})
        decision, sti = self.decision_maker.decide(prob)

        return decision, sti

    def letter_info(
            self, triggers: List[Tuple[str, float]], target_info: List[str]
    ) -> Tuple[List[str], List[float], List[str]]:
        """
        Filters out non-letters and separates timings from letters.
        Parameters:
        -----------
         triggers: triggers e.g. [['A', 0.5], ...]
                as letter and flash time for the letter
         target_info: target information about the stimuli;
            ex. ['nontarget', 'nontarget', ...]
        Returns:
        --------
            (letters, times, target_info)
        """
        letters = []
        times = []
        target_types = []

        for i, (letter, stamp) in enumerate(triggers):
            if letter not in self.nonletters:
                letters.append(letter)
                times.append(stamp)
                target_types.append(target_info[i])

        # Raise an error if the stimuli includes unexpected terms
        if not set(letters).issubset(self.valid_targets):
            invalid = set(letters).difference(self.valid_targets)
            raise Exception(
                f'unexpected letters received in copy phrase: {invalid}')

        return letters, times, target_types

    def initialize_epoch(self):
        """If a decision is made initializes the next epoch."""

        try:
            # First, reset the history for this new epoch
            self.conjugator.reset_history()

            # If there is no language model specified, mock the LM prior
            # TODO: is the probability domain correct? ERP evidence is in
            # the log domain; LM by default returns negative log domain.
            if not self.lmodel:
                # mock probabilities to be equally likely for all letters.
                overrides = {BACKSPACE_CHAR: self.backspace_prob}
                prior = equally_probable(self.alp, overrides)

            # Else, let's query the lmodel for priors
            else:
                # Get the displayed state
                # TODO: for oclm this should be a list of (sym, prob)
                update = self.decision_maker.displayed_state

                # update the lmodel and get back the priors
                lm_prior = self.lmodel.state_update(update)

                # normalize to probability domain if needed
                if getattr(self.lmodel, 'normalized', False):
                    lm_letter_prior = lm_prior['letter']
                else:
                    lm_letter_prior = norm_domain(lm_prior['letter'])

                if BACKSPACE_CHAR in self.alp:
                    # Append backspace if missing.
                    sym = (BACKSPACE_CHAR, self.backspace_prob)
                    lm_letter_prior = sym_appended(lm_letter_prior, sym)

                # convert to format needed for evidence fusion;
                # probability value only in alphabet order.
                # TODO: ensure that probabilities still add to 1.0
                prior = [
                    prior_prob for alp_letter in self.alp
                    for prior_sym, prior_prob in lm_letter_prior
                    if alp_letter == prior_sym
                ]

            # Try fusing the lmodel evidence
            try:
                prob_dist = self.conjugator.update_and_fuse(
                    {'LM': np.array(prior)})
            except Exception as lm_exception:
                print("Error updating language model!")
                raise lm_exception

            # Get decision maker to give us back some decisions and stimuli
            is_accepted, sti = self.decision_maker.decide(prob_dist)

        except Exception as init_exception:
            print("Error in initialize_epoch: %s" % (init_exception))
            raise init_exception

        return is_accepted, sti
Exemple #5
0
class TestDecisionMaker(unittest.TestCase):
    """Test for decision maker class """
    def setUp(self):
        """Set up decision maker object for testing """
        alphabet = list(string.ascii_uppercase) + ['<'] + [SPACE_CHAR]
        stopping_criteria = CriteriaEvaluator(
            continue_criteria=[MinIterationsCriteria(min_num_inq=1)],
            commit_criteria=[
                MaxIterationsCriteria(max_num_inq=10),
                ProbThresholdCriteria(threshold=0.8)
            ])

        stimuli_agent = NBestStimuliAgent(alphabet=alphabet, len_query=10)
        self.decision_maker = DecisionMaker(
            stimuli_agent=stimuli_agent,
            stopping_evaluator=stopping_criteria,
            state='',
            alphabet=alphabet,
            is_txt_stim=True,
            stimuli_timing=[1, .2],
            inq_constants=None)

        self.evidence_fusion = EvidenceFusion(list_name_evidence=['A', 'B'],
                                              len_dist=2)

    def tearDown(self):
        """Reset decision maker and evidence fusion at the
        end of each test. """
        self.decision_maker.reset()
        self.evidence_fusion.reset_history()

    def test_evidence_fusion_init(self):
        self.assertEqual(self.evidence_fusion.evidence_history, {
            'A': [],
            'B': []
        })
        self.assertEqual(self.evidence_fusion.likelihood[0], [0.5])
        self.assertEqual(self.evidence_fusion.likelihood[1], [0.5])

    def test_reset_history(self):
        self.evidence_fusion.reset_history()
        self.assertEqual(self.evidence_fusion.evidence_history, {
            'A': [],
            'B': []
        })
        self.assertEqual(self.evidence_fusion.likelihood[0], [0.5])
        self.assertEqual(self.evidence_fusion.likelihood[1], [0.5])

    def test_update_and_fuse_with_float_evidence(self):
        dict_evidence = {'A': [0.5], 'B': [0.5]}
        self.evidence_fusion.update_and_fuse(dict_evidence)
        self.assertEqual(self.evidence_fusion.evidence_history['A'][0],
                         dict_evidence['A'])
        self.assertEqual(self.evidence_fusion.evidence_history['B'][0],
                         dict_evidence['B'])
        self.assertEqual(self.evidence_fusion.likelihood[0], [[0.5]])
        self.assertEqual(self.evidence_fusion.likelihood[1], [[0.5]])

    def test_update_and_fuse_with_inf_evidence(self):
        dict_evidence = {'A': [0.5], 'B': [math.inf]}
        self.evidence_fusion.update_and_fuse(dict_evidence)
        self.assertEqual(self.evidence_fusion.evidence_history['A'][0],
                         dict_evidence['A'])
        self.assertEqual(self.evidence_fusion.evidence_history['B'][0],
                         dict_evidence['B'])
        self.assertEqual(self.evidence_fusion.likelihood[0], [1.0])
        self.assertEqual(self.evidence_fusion.likelihood[1], [0.0])

    def test_save_history(self):
        history = self.evidence_fusion.save_history()
        self.assertEqual(0, history)

    def test_decision_maker_init(self):
        """Test initialization"""
        # TODO: Update that test part
        # self.assertEqual(self.decision_maker.min_num_inq, 1)
        # self.assertEqual(self.decision_maker.max_num_inq, 3)
        self.assertEqual(self.decision_maker.state, '')
        self.assertEqual(self.decision_maker.displayed_state, '')

    def test_update_with_letter(self):
        """Test update method with letter being the new state"""
        old_displayed_state = self.decision_maker.displayed_state
        old_state = self.decision_maker.state
        new_state = 'E'
        self.decision_maker.update(state=new_state)
        self.assertEqual(self.decision_maker.state, old_state + new_state)
        self.assertEqual(self.decision_maker.displayed_state,
                         old_displayed_state + 'E')

    def test_update_with_backspace(self):
        """Test update method with backspace being the new state"""
        old_displayed_state = self.decision_maker.displayed_state
        old_state = self.decision_maker.state
        new_state = '<'
        self.decision_maker.update(state=new_state)
        self.assertEqual(self.decision_maker.state, old_state + new_state)
        self.assertEqual(self.decision_maker.displayed_state,
                         old_displayed_state[0:-1])
        self.assertLess(len(self.decision_maker.displayed_state),
                        len(self.decision_maker.state))

    def test_reset(self):
        """Test reset of decision maker state"""
        self.decision_maker.reset()
        self.assertEqual(self.decision_maker.state, '')
        self.assertEqual(self.decision_maker.displayed_state, '')
        self.assertEqual(self.decision_maker.time, 0)
        self.assertEqual(self.decision_maker.inquiry_counter, 0)

    def test_form_display_state(self):
        """Test form display state method with a dummy state"""
        self.decision_maker.update(state='ABC<.E')
        self.decision_maker.form_display_state(self.decision_maker.state)
        self.assertEqual(self.decision_maker.displayed_state, 'ABE')
        self.decision_maker.reset()

    def test_do_series(self):
        """Test do_series method"""
        probability_distribution = np.ones(len(
            self.decision_maker.alphabet)) / 8
        decision, chosen_stimuli = self.decision_maker.decide(
            probability_distribution)
        self.decision_maker.do_series()
        self.assertEqual(self.decision_maker.inquiry_counter, 0)

    def test_decide_state_update(self):
        """Tests decide state update method"""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        self.decision_maker.list_series[-1]['list_distribution'].append(
            probability_distribution)
        decision = self.decision_maker.decide_state_update()
        expected = 'A'  # expect to commit to first letter in inquiry, due to uniform probability
        self.assertEqual(decision, 'A')

    def test_schedule_inquiry(self):
        """Test inquiry scheduling. Should return new stimuli list, at random."""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        old_counter = self.decision_maker.inquiry_counter
        self.decision_maker.list_series[-1]['list_distribution'].append(
            probability_distribution)
        stimuli = self.decision_maker.schedule_inquiry()
        self.assertEqual(self.decision_maker.state, '.')
        self.assertEqual(stimuli[0],
                         self.decision_maker.list_series[-1]['list_sti'][-1])
        self.assertLess(old_counter, self.decision_maker.inquiry_counter)

    def test_prepare_stimuli(self):
        """Test that stimuli are prepared as expected"""
        probability_distribution = np.ones(len(self.decision_maker.alphabet))
        self.decision_maker.list_series[-1]['list_distribution'].append(
            probability_distribution)
        stimuli = self.decision_maker.prepare_stimuli()
        self.assertEqual(self.decision_maker.stimuli_agent.len_query + 1,
                         len(stimuli[0][0]))
        for i in range(1, len(stimuli[0][0])):
            self.assertIn(stimuli[0][0][i], self.decision_maker.alphabet)
        self.assertEqual(stimuli[1][0][0:2],
                         self.decision_maker.stimuli_timing)