diff --git a/convlab/__init__.py b/convlab/__init__.py index 43cc4bc75aab6c4576a4097a549079e521d20e20..4b14a707ee43021fc97936c5f64d95c2cb7aa659 100755 --- a/convlab/__init__.py +++ b/convlab/__init__.py @@ -1,4 +1,3 @@ - import os from convlab.nlu import NLU from convlab.dst import DST diff --git a/convlab/dst/__init__.py b/convlab/dst/__init__.py index f356535526b816efc3ae4df52345d9c7e63f3f64..53243d54c53824d2e887b02e3fea56fc6f40082a 100755 --- a/convlab/dst/__init__.py +++ b/convlab/dst/__init__.py @@ -1,2 +1 @@ from convlab.dst.dst import DST -from convlab.dst.setsumbt import SetSUMBTTracker diff --git a/convlab/dst/setsumbt/tracker.py b/convlab/dst/setsumbt/tracker.py index 9b2358614c2edb117bdd930a66568ce7db17046d..b58fc5bd17fdf486e544fe06c4f8c2bc0b83f8b3 100644 --- a/convlab/dst/setsumbt/tracker.py +++ b/convlab/dst/setsumbt/tracker.py @@ -223,10 +223,9 @@ class SetSUMBTTracker(DST): user_acts = _output[2] for domain in new_domains: - user_acts.append({'intent': 'inform', 'domain': domain, 'slot': '', 'value': ''}) + user_acts.append(['inform', domain, 'none', 'none']) new_belief_state = copy.deepcopy(prev_state['belief_state']) - # user_acts = [] for domain, substate in _output[0].items(): for slot, value in substate.items(): value = '' if value == 'none' else value @@ -247,7 +246,7 @@ class SetSUMBTTracker(DST): new_belief_state[domain][slot] = value if prev_state['belief_state'][domain][slot] != value: - user_acts.append({'intent': 'inform', 'domain': domain, 'slot': slot, 'value': value}) + user_acts.append(['inform', domain, slot, value]) else: bug = f'Unknown slot name <{slot}> with value <{value}> of domain <{domain}>' logging.debug(bug) @@ -345,10 +344,7 @@ class SetSUMBTTracker(DST): # Construct request action prediction request_acts = [slot for slot, p in request_probs.items() if p[0, 0].item() > 0.5] request_acts = [slot.split('-', 1) for slot in request_acts] - request_acts = [{'intent': 'request', - 'domain': domain, - 'slot': slot, - 'value': '?'} for domain, slot in request_acts] + request_acts = [['request', domain, slot, '?'] for domain, slot in request_acts] # Construct active domain set active_domains = {domain: p[0, 0].item() > 0.5 for domain, p in active_domain_probs.items()} @@ -356,7 +352,7 @@ class SetSUMBTTracker(DST): # Construct general domain action general_acts = general_act_probs[0, 0, :].argmax(-1).item() general_acts = [[], ['bye'], ['thank']][general_acts] - general_acts = [{'intent': act, 'domain': 'general', 'slot': '', 'value': ''} for act in general_acts] + general_acts = [[act, 'general', 'none', 'none'] for act in general_acts] user_acts = request_acts + general_acts @@ -417,22 +413,30 @@ class SetSUMBTTracker(DST): return features -if __name__ == "__main__": - tracker = SetSUMBTTracker(model_path='/gpfs/project/niekerk/src/SetSUMBT/models/SetSUMBT+ActPrediction-multiwoz21-roberta-gru-cosine-labelsmoothing-Seed0-10-08-22-12-42', - return_turn_pooled_representation=True, return_confidence_scores=True, - confidence_threshold = 'auto', return_belief_state_entropy=True, - return_belief_state_mutual_info=True, store_full_belief_state=True) - tracker.init_session() - state = tracker.update('hey. I need a cheap restaurant.') - tracker.state['history'].append(['usr', 'hey. I need a cheap restaurant.']) - tracker.state['history'].append(['sys', 'There are many cheap places, which food do you like?']) - state = tracker.update('If you have something Asian that would be great.') - tracker.state['history'].append(['usr', 'If you have something Asian that would be great.']) - tracker.state['history'].append(['sys', 'The Golden Wok is a nice cheap chinese restaurant.']) - tracker.state['system_action'] = [{'intent': 'inform', 'domain': 'restaurant', 'slot': 'food', 'value': 'chinese'}, - {'intent': 'inform', 'domain': 'restaurant', 'slot': 'name', - 'value': 'the golden wok'}] - state = tracker.update('Great. Where are they located?') - tracker.state['history'].append(['usr', 'Great. Where are they located?']) - print(tracker.state) - print(tracker.full_belief_state) +# if __name__ == "__main__": +# from convlab.policy.vector.vector_uncertainty import VectorUncertainty +# # from convlab.policy.vector.vector_binary import VectorBinary +# tracker = SetSUMBTTracker(model_path='/gpfs/project/niekerk/src/SetSUMBT/models/SetSUMBT+ActPrediction-multiwoz21-roberta-gru-cosine-labelsmoothing-Seed0-10-08-22-12-42', +# return_confidence_scores=True, confidence_threshold='auto', +# return_belief_state_entropy=True) +# vector = VectorUncertainty(use_state_total_uncertainty=True, confidence_thresholds=tracker.confidence_thresholds, +# use_masking=True) +# # vector = VectorBinary() +# tracker.init_session() +# +# state = tracker.update('hey. I need a cheap restaurant.') +# tracker.state['history'].append(['usr', 'hey. I need a cheap restaurant.']) +# tracker.state['history'].append(['sys', 'There are many cheap places, which food do you like?']) +# state = tracker.update('If you have something Asian that would be great.') +# tracker.state['history'].append(['usr', 'If you have something Asian that would be great.']) +# tracker.state['history'].append(['sys', 'The Golden Wok is a nice cheap chinese restaurant.']) +# tracker.state['system_action'] = [['inform', 'restaurant', 'food', 'chinese'], +# ['inform', 'restaurant', 'name', 'the golden wok']] +# state = tracker.update('Great. Where are they located?') +# tracker.state['history'].append(['usr', 'Great. Where are they located?']) +# state = tracker.state +# state['terminated'] = False +# state['booked'] = {} +# +# print(state) +# print(vector.state_vectorize(state)) diff --git a/convlab/policy/vector/vector_multiwoz_uncertainty.py b/convlab/policy/vector/vector_multiwoz_uncertainty.py deleted file mode 100644 index 6a0850f4ce7c791f7da99c9c8b9e632eac543ba2..0000000000000000000000000000000000000000 --- a/convlab/policy/vector/vector_multiwoz_uncertainty.py +++ /dev/null @@ -1,238 +0,0 @@ -# -*- coding: utf-8 -*- -import sys -import os -import numpy as np -import logging -from convlab.util.multiwoz.lexicalize import delexicalize_da, flat_da -from convlab.util.multiwoz.state import default_state -from convlab.util.multiwoz.multiwoz_slot_trans import REF_SYS_DA -from .vector_binary import VectorBinary as VectorBase - -DEFAULT_INTENT_FILEPATH = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname( - os.path.dirname(os.path.abspath(__file__))))), - 'data/multiwoz/trackable_intent.json' -) - - -SLOT_MAP = {'taxi_types': 'car type'} - - -class MultiWozVector(VectorBase): - - def __init__(self, voc_file=None, voc_opp_file=None, character='sys', - intent_file=DEFAULT_INTENT_FILEPATH, - use_confidence_scores=False, - use_entropy=False, - use_mutual_info=False, - use_masking=False, - manually_add_entity_names=False, - seed=0, - shrink=False): - - self.use_confidence_scores = use_confidence_scores - self.use_entropy = use_entropy - self.use_mutual_info = use_mutual_info - self.thresholds = None - - super().__init__(voc_file, voc_opp_file, character, intent_file, use_masking, manually_add_entity_names, seed) - - def get_state_dim(self): - self.belief_state_dim = 0 - for domain in self.belief_domains: - for slot in default_state()['belief_state'][domain.lower()]['semi']: - # Dim 1 - indicator/confidence score - # Dim 2 - Entropy (Total uncertainty) / Mutual information (knowledge unc) - slot_dim = 1 if not self.use_entropy else 2 - slot_dim += 1 if self.use_mutual_info else 0 - self.belief_state_dim += slot_dim - - self.state_dim = self.da_opp_dim + self.da_dim + self.belief_state_dim + \ - len(self.db_domains) + 6 * len(self.db_domains) + 1 - - def dbquery_domain(self, domain): - """ - query entities of specified domain - Args: - domain string: - domain to query - Returns: - entities list: - list of entities of the specified domain - """ - # Get all user constraints - constraint = self.state[domain.lower()]['semi'] - constraint = {k: i for k, i in constraint.items() if i and i not in ['dontcare', "do n't care", "do not care"]} - - # Remove constraints for which the uncertainty is high - if self.confidence_scores is not None and self.use_confidence_scores and self.thresholds != None: - # Collect threshold values for each domain-slot pair - thres = self.thresholds.get(domain.lower(), {}) - thres = {k: thres.get(k, 0.05) for k in constraint} - # Get confidence scores for each constraint - probs = self.confidence_scores.get(domain.lower(), {}) - probs = {k: probs.get(k, {}).get('inform', 1.0) - for k in constraint} - - # Filter out constraints for which confidence is lower than threshold - constraint = {k: i for k, i in constraint.items() - if probs[k] >= thres[k]} - - return self.db.query(domain.lower(), constraint.items()) - - # Add thresholds for db_queries - def setup_uncertain_query(self, thresholds): - self.use_confidence_scores = True - self.thresholds = thresholds - logging.info('DB Search uncertainty activated.') - - def vectorize_user_act_confidence_scores(self, state, opp_action): - """Return confidence scores for the user actions""" - opp_act_vec = np.zeros(self.da_opp_dim) - for da in self.opp2vec: - domain, intent, slot, value = da.split('-') - if domain.lower() in state['belief_state_probs']: - # Map slot name to match user actions - slot = REF_SYS_DA[domain].get( - slot, slot) if domain in REF_SYS_DA else slot - slot = slot if slot else 'none' - slot = SLOT_MAP.get(slot, slot) - domain = domain.lower() - - if slot in state['belief_state_probs'][domain]: - prob = state['belief_state_probs'][domain][slot] - elif slot.lower() in state['belief_state_probs'][domain]: - prob = state['belief_state_probs'][domain][slot.lower()] - else: - prob = {} - - intent = intent.lower() - if intent in prob: - prob = float(prob[intent]) - elif da in opp_action: - prob = 1.0 - else: - prob = 0.0 - elif da in opp_action: - prob = 1.0 - else: - prob = 0.0 - opp_act_vec[self.opp2vec[da]] = prob - - return opp_act_vec - - def state_vectorize(self, state): - """vectorize a state - - Args: - state (dict): - Dialog state - action (tuple): - Dialog act - Returns: - state_vec (np.array): - Dialog state vector - """ - self.state = state['belief_state'] - self.confidence_scores = state['belief_state_probs'] if 'belief_state_probs' in state else None - domain_active_dict = {} - for domain in self.belief_domains: - domain_active_dict[domain] = False - - # when character is sys, to help query database when da is booking-book - # update current domain according to user action - if self.character == 'sys': - action = state['user_action'] - for intent, domain, slot, value in action: - domain_active_dict[domain] = True - - action = state['user_action'] if self.character == 'sys' else state['system_action'] - opp_action = delexicalize_da(action, self.requestable) - opp_action = flat_da(opp_action) - if 'belief_state_probs' in state and self.use_confidence_scores: - opp_act_vec = self.vectorize_user_act_confidence_scores( - state, opp_action) - else: - opp_act_vec = np.zeros(self.da_opp_dim) - for da in opp_action: - if da in self.opp2vec: - prob = 1.0 - opp_act_vec[self.opp2vec[da]] = prob - - action = state['system_action'] if self.character == 'sys' else state['user_action'] - action = delexicalize_da(action, self.requestable) - action = flat_da(action) - last_act_vec = np.zeros(self.da_dim) - for da in action: - if da in self.act2vec: - last_act_vec[self.act2vec[da]] = 1. - - belief_state = np.zeros(self.belief_state_dim) - i = 0 - for domain in self.belief_domains: - if self.use_confidence_scores and 'belief_state_probs' in state: - for slot in state['belief_state'][domain.lower()]['semi']: - if slot in state['belief_state_probs'][domain.lower()]: - prob = state['belief_state_probs'][domain.lower() - ][slot] - prob = prob['inform'] if 'inform' in prob else None - if prob: - belief_state[i] = float(prob) - i += 1 - else: - for slot, value in state['belief_state'][domain.lower()]['semi'].items(): - if value and value != 'not mentioned': - belief_state[i] = 1. - i += 1 - if 'active_domains' in state: - domain_active = state['active_domains'][domain.lower()] - domain_active_dict[domain] = domain_active - else: - if [slot for slot, value in state['belief_state'][domain.lower()]['semi'].items() if value]: - domain_active_dict[domain] = True - - # Add knowledge and/or total uncertainty to the belief state - if self.use_entropy and 'entropy' in state: - for domain in self.belief_domains: - for slot in state['belief_state'][domain.lower()]['semi']: - if slot in state['entropy'][domain.lower()]: - belief_state[i] = float( - state['entropy'][domain.lower()][slot]) - i += 1 - - if self.use_mutual_info and 'mutual_information' in state: - for domain in self.belief_domains: - for slot in state['belief_state'][domain.lower()]['semi']: - if slot in state['mutual_information'][domain.lower()]: - belief_state[i] = float( - state['mutual_information'][domain.lower()][slot]) - i += 1 - - book = np.zeros(len(self.db_domains)) - for i, domain in enumerate(self.db_domains): - if state['belief_state'][domain.lower()]['book']['booked']: - book[i] = 1. - - degree, number_entities_dict = self.pointer() - - final = 1. if state['terminated'] else 0. - - state_vec = np.r_[opp_act_vec, last_act_vec, - belief_state, book, degree, final] - assert len(state_vec) == self.state_dim - - if self.use_mask is not None: - # None covers the case for policies that don't use masking at all, so do not expect an output "state_vec, mask" - if self.use_mask: - domain_mask = self.compute_domain_mask(domain_active_dict) - entity_mask = self.compute_entity_mask(number_entities_dict) - general_mask = self.compute_general_mask() - mask = domain_mask + entity_mask + general_mask - for i in range(self.da_dim): - mask[i] = -int(bool(mask[i])) * sys.maxsize - else: - mask = np.zeros(self.da_dim) - - return state_vec, mask - else: - return state_vec diff --git a/convlab/policy/vector/vector_uncertainty.py b/convlab/policy/vector/vector_uncertainty.py new file mode 100644 index 0000000000000000000000000000000000000000..ee4582467925b76b2c1472f445e057808cfda9fc --- /dev/null +++ b/convlab/policy/vector/vector_uncertainty.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +import sys +import numpy as np +import logging + +from convlab.util.multiwoz.lexicalize import delexicalize_da, flat_da +from convlab.policy.vector.vector_binary import VectorBinary + + +class VectorUncertainty(VectorBinary): + """Vectorise state and state uncertainty predictions""" + + def __init__(self, + dataset_name: str = 'multiwoz21', + character: str = 'sys', + use_masking: bool = False, + manually_add_entity_names: bool = True, + seed: str = 0, + use_confidence_scores: bool = True, + confidence_thresholds: dict = None, + use_state_total_uncertainty: bool = False, + use_state_knowledge_uncertainty: bool = False): + """ + Args: + dataset_name: Name of environment dataset + character: Character of the agent (sys/usr) + use_masking: If true certain actions are masked during devectorisation + manually_add_entity_names: If true inform entity name actions are manually added + seed: Seed + use_confidence_scores: If true confidence scores are used in state vectorisation + confidence_thresholds: If true confidence thresholds are used in database querying + use_state_total_uncertainty: If true state entropy is added to the state vector + use_state_knowledge_uncertainty: If true state mutual information is added to the state vector + """ + + self.use_confidence_scores = use_confidence_scores + self.use_state_total_uncertainty = use_state_total_uncertainty + self.use_state_knowledge_uncertainty = use_state_knowledge_uncertainty + if confidence_thresholds is not None: + self.setup_uncertain_query(confidence_thresholds) + + super().__init__(dataset_name, character, use_masking, manually_add_entity_names, seed) + + def get_state_dim(self): + self.belief_state_dim = 0 + + for domain in self.ontology['state']: + for slot in self.ontology['state'][domain]: + # Dim 1 - indicator/confidence score + # Dim 2 - Entropy (Total uncertainty) / Mutual information (knowledge unc) + slot_dim = 1 if not self.use_state_total_uncertainty else 2 + slot_dim += 1 if self.use_state_knowledge_uncertainty else 0 + self.belief_state_dim += slot_dim + + self.state_dim = self.da_opp_dim + self.da_dim + self.belief_state_dim + \ + len(self.db_domains) + 6 * len(self.db_domains) + 1 + + # Add thresholds for db_queries + def setup_uncertain_query(self, confidence_thresholds): + self.use_confidence_scores = True + self.confidence_thresholds = confidence_thresholds + logging.info('DB Search uncertainty activated.') + + def dbquery_domain(self, domain): + """ + query entities of specified domain + Args: + domain string: + domain to query + Returns: + entities list: + list of entities of the specified domain + """ + # Get all user constraints + constraints = {slot: value for slot, value in self.state[domain].items() + if slot and value not in ['dontcare', + "do n't care", "do not care"]} if domain in self.state else dict() + + # Remove constraints for which the uncertainty is high + if self.confidence_scores is not None and self.use_confidence_scores and self.confidence_thresholds is not None: + # Collect threshold values for each domain-slot pair + threshold = self.confidence_thresholds.get(domain, dict()) + threshold = {slot: threshold.get(slot, 0.05) for slot in constraints} + # Get confidence scores for each constraint + probs = self.confidence_scores.get(domain, dict()) + probs = {slot: probs.get(slot, {}).get('inform', 1.0) for slot in constraints} + + # Filter out constraints for which confidence is lower than threshold + constraints = {slot: value for slot, value in constraints.items() if probs[slot] >= threshold[slot]} + + return self.db.query(domain, constraints.items(), topk=10) + + def vectorize_user_act(self, state): + """Return confidence scores for the user actions""" + self.confidence_scores = state['belief_state_probs'] if 'belief_state_probs' in state else None + action = state['user_action'] if self.character == 'sys' else state['system_action'] + opp_action = delexicalize_da(action, self.requestable) + opp_action = flat_da(opp_action) + opp_act_vec = np.zeros(self.da_opp_dim) + for da in opp_action: + if da in self.opp2vec: + if 'belief_state_probs' in state and self.use_confidence_scores: + domain, intent, slot, value = da.split('-') + if domain in state['belief_state_probs']: + slot = slot if slot else 'none' + if slot in state['belief_state_probs'][domain]: + prob = state['belief_state_probs'][domain][slot] + elif slot.lower() in state['belief_state_probs'][domain]: + prob = state['belief_state_probs'][domain][slot.lower()] + else: + prob = dict() + + if intent in prob: + prob = float(prob[intent]) + else: + prob = 1.0 + else: + prob = 1.0 + else: + prob = 1.0 + opp_act_vec[self.opp2vec[da]] = prob + + return opp_act_vec + + def vectorize_belief_state(self, state, domain_active_dict): + belief_state = np.zeros(self.belief_state_dim) + i = 0 + for domain in self.belief_domains: + if self.use_confidence_scores and 'belief_state_probs' in state: + for slot in state['belief_state'][domain]: + prob = None + if slot in state['belief_state_probs'][domain]: + prob = state['belief_state_probs'][domain][slot] + prob = prob['inform'] if 'inform' in prob else None + if prob: + belief_state[i] = float(prob) + i += 1 + else: + for slot, value in state['belief_state'][domain].items(): + if value and value != 'not mentioned': + belief_state[i] = 1. + i += 1 + + if 'active_domains' in state: + domain_active = state['active_domains'][domain] + domain_active_dict[domain] = domain_active + else: + if [slot for slot, value in state['belief_state'][domain].items() if value]: + domain_active_dict[domain] = True + + # Add knowledge and/or total uncertainty to the belief state + if self.use_state_total_uncertainty and 'entropy' in state: + for domain in self.belief_domains: + for slot in state['belief_state'][domain]: + if slot in state['entropy'][domain]: + belief_state[i] = float(state['entropy'][domain][slot]) + i += 1 + + if self.use_state_knowledge_uncertainty and 'mutual_information' in state: + for domain in self.belief_domains: + for slot in state['belief_state'][domain]['semi']: + if slot in state['mutual_information'][domain]: + belief_state[i] = float(state['mutual_information'][domain][slot]) + i += 1 + + return belief_state, domain_active_dict