Esempio n. 1
0
    def __init__(self, data_dir,
                 goal_model_path,
                 corpus_path=None,
                 boldify=False):
        """
        Args:
            goal_model_path: path to a goal model 
            corpus_path: path to a dialog corpus to build a goal model 
        """
        self.dbquery = DBQuery(data_dir)
        self.goal_model_path = data_dir + '/' + goal_model_path
        self.corpus_path = data_dir + '/' + corpus_path if corpus_path is not None else None
        self.boldify = do_boldify if boldify else null_boldify
        if os.path.exists(self.goal_model_path):
            with open(self.goal_model_path, 'rb') as f:
                self.ind_slot_dist, self.ind_slot_value_dist, self.domain_ordering_dist, self.book_dist = pickle.load(f)
            logging.info('Loading goal model is done')
        else:
            self._build_goal_model()
            logging.info('Building goal model is done')

        # remove some slot
        del self.ind_slot_dist['police']['reqt']['postcode']
        del self.ind_slot_value_dist['police']['reqt']['postcode']
        del self.ind_slot_dist['hospital']['reqt']['postcode']
        del self.ind_slot_value_dist['hospital']['reqt']['postcode']
        del self.ind_slot_dist['hospital']['reqt']['address']
        del self.ind_slot_value_dist['hospital']['reqt']['address']
Esempio n. 2
0
 def __init__(self, data_dir, config):
     self.time_step = 0
     self.cfg = config
     self.db = DBQuery(data_dir, config)
     self.topic = ''
     self.evaluator = MultiWozEvaluator(data_dir)
     self.lock_evalutor = False
Esempio n. 3
0
    def __init__(self, data_dir, cfg, domain=None):
        """
        DataManager的两种加载方式,第一种是直接通过源文件进行加载,还有一种是通过已经处理好的数据进行加载
        DataManager 只会在预训练的时候使用到
        """
        # 主要实现对data和goal的管理
        self.data = {}
        self.goal = {}

        if domain:
            self.data_dir_new = data_dir + '/processed_data_' + domain
        else:
            self.data_dir_new = data_dir + '/processed_data'

        # **********************
        # 实验一 2020/10/15
        # 对原始数据,按照domain划分为不同的文件,对于对话中同时具有多个domain的暂时不做处理。
        # 验证MADPL对单个domain的效果
        # **********************
        if os.path.exists(self.data_dir_new):
            # 对于已经划分并转化后的train valid test的文件进行读取
            logging.info('Load processed data file')
            for part in ['train', 'valid', 'test']:
                with open(self.data_dir_new + '/' + part + '.json', 'r') as f:
                    self.data[part] = json.load(f)
                with open(self.data_dir_new + '/' + part + '_goal.json',
                          'r') as f:
                    self.goal[part] = json.load(f)
        else:
            # 否则基于数据库完成数据的原始构件
            from dbquery import DBQuery
            db = DBQuery(data_dir, cfg)
            logging.info('Start preprocessing the dataset')
            # 创建session数据
            self._build_data(data_dir, self.data_dir_new, cfg, db)

        # 构建pt文件, 记录pytorch训练数据
        for part in ['train', 'valid', 'test']:
            file_dir = self.data_dir_new + '/' + part + '_sys.pt'
            if not os.path.exists(file_dir):
                from dbquery import DBQuery
                db = DBQuery(data_dir, cfg)
                self.create_dataset_sys(part, file_dir, data_dir, cfg, db)

            file_dir = self.data_dir_new + '/' + part + '_usr.pt'
            if not os.path.exists(file_dir):
                from dbquery import DBQuery
                db = DBQuery(data_dir, cfg)
                self.create_dataset_usr(part, file_dir, data_dir, cfg, db)

            file_dir = self.data_dir_new + '/' + part + '_glo.pt'
            if not os.path.exists(file_dir):
                from dbquery import DBQuery
                db = DBQuery(data_dir, cfg)
                self.create_dataset_global(part, file_dir, data_dir, cfg, db)
Esempio n. 4
0
    def __init__(self,
                 data_dir,
                 cfg,
                 goal_model_path,
                 corpus_path=None,
                 boldify=False):
        """
        生成各种分布:
        cfg 这里只用于为dbquery提供地址,并没有提供对数据分布进行控制
        self.ind_slot_dist: 独立实体分布
        self.ind_slot_value_dist: 独立实体值分布
        self.domain_ordering_dist: domain请求分布
        self.book_dist: 订阅分布
        Args:
            goal_model_path: path to a goal model
            corpus_path: path to a dialog corpus to build a goal model
        """
        self.cfg = cfg
        if self.cfg.d:
            self.domains = [self.cfg.d]
        else:
            print(self.cfg)
            self.domains = {
                'attraction', 'hotel', 'restaurant', 'train', 'taxi',
                'hospital', 'police'
            }
        self.dbquery = DBQuery(data_dir, cfg)
        self.goal_model_path = data_dir + '/' + goal_model_path
        self.corpus_path = data_dir + '/' + corpus_path if corpus_path is not None else None
        self.boldify = do_boldify if boldify else null_boldify
        if os.path.exists(self.goal_model_path):
            with open(self.goal_model_path, 'rb') as f:
                self.ind_slot_dist, self.ind_slot_value_dist, self.domain_ordering_dist, self.book_dist = pickle.load(
                    f)
            logging.info('Loading goal model is done')
        else:
            self._build_goal_model()
            logging.info('Building goal model is done')

        # remove some slot
        if 'police' in self.ind_slot_dist:
            del self.ind_slot_dist['police']['reqt']['postcode']
            del self.ind_slot_value_dist['police']['reqt']['postcode']
        if 'hospital' in self.ind_slot_dist:
            del self.ind_slot_dist['hospital']['reqt']['postcode']
            del self.ind_slot_value_dist['hospital']['reqt']['postcode']
            del self.ind_slot_dist['hospital']['reqt']['address']
            del self.ind_slot_value_dist['hospital']['reqt']['address']
Esempio n. 5
0
    def __init__(self, args, manager, config, pretrain=False):

        voc_goal_size, voc_usr_size, voc_sys_size = manager.get_voc_size()
        self.user = VHUS(config, voc_goal_size, voc_usr_size,
                         voc_sys_size).to(device=DEVICE)
        self.optim = optim.Adam(self.user.parameters(), lr=args.lr_simu)
        self.goal_gen = GoalGenerator(
            args.data_dir,
            goal_model_path='processed_data/goal_model.pkl',
            corpus_path=config.data_file)
        self.cfg = config
        self.manager = manager
        self.user.eval()

        if pretrain:
            self.print_per_batch = args.print_per_batch
            self.save_dir = args.save_dir
            self.save_per_epoch = args.save_per_epoch
            seq_goals, seq_usr_dass, seq_sys_dass = manager.data_loader_seg()
            train_goals, train_usrdas, train_sysdas, \
            test_goals, test_usrdas, test_sysdas, \
            val_goals, val_usrdas, val_sysdas = manager.train_test_val_split_seg(
                seq_goals, seq_usr_dass, seq_sys_dass)
            self.data_train = (train_goals, train_usrdas, train_sysdas,
                               args.batchsz)
            self.data_valid = (val_goals, val_usrdas, val_sysdas, args.batchsz)
            self.data_test = (test_goals, test_usrdas, test_sysdas,
                              args.batchsz)
            self.nll_loss = nn.NLLLoss(ignore_index=0)  # PAD=0
            self.bce_loss = nn.BCEWithLogitsLoss()
        else:
            from dbquery import DBQuery
            self.db = DBQuery(args.data_dir)
Esempio n. 6
0
 def __init__(self, args, manager, config, pretrain=False, inference=False):
     
     self.irl = AIRL(config, args.gamma).to(device=DEVICE)
     
     self.bce_loss = nn.BCEWithLogitsLoss()
     self.step = 0
     self.anneal = args.anneal
     self.irl_params = self.irl.parameters()
     self.irl_optim = optim.RMSprop(self.irl_params, lr=args.lr_irl)
     self.weight_cliping_limit = args.clip
     
     self.save_dir = args.save_dir
     self.save_per_epoch = args.save_per_epoch
     self.optim_batchsz = args.batchsz
     self.irl.eval()
     
     db = DBQuery(args.data_dir)
     if pretrain:
         self.print_per_batch = args.print_per_batch
         self.data_train = manager.create_dataset_irl('train', args.batchsz, config, db)
         self.data_valid = manager.create_dataset_irl('valid', args.batchsz, config, db)
         self.data_test = manager.create_dataset_irl('test', args.batchsz, config, db)
         self.irl_iter = iter(self.data_train)
         self.irl_iter_valid = iter(self.data_valid)
         self.irl_iter_test = iter(self.data_test)
     elif not inference:
         self.data_train = manager.create_dataset_irl('train', args.batchsz, config, db)
         self.data_valid = manager.create_dataset_irl('valid', args.batchsz, config, db)
         self.irl_iter = iter(self.data_train)
         self.irl_iter_valid = iter(self.data_valid)
Esempio n. 7
0
def test_connection(connstr):
    sqlstr = ''     #SQL string will be blank.  It's not needed yet, but one has to be passed to the DBQuery class
    fatal_error = False
    try:
        #Call class to test the connection
        myquery = DBQuery(connstr, sqlstr)
        cnxn = myquery.get_connection()
        if cnxn != None:
            print('Database Connection was Successful.')
        else:
            print('Database Connection Failed.  Ending Program.')
            fatal_error = True
    except Exception as err:
        print('Database Connection Failed. Ending program.')
        print(err)
        fatal_error = True
    return fatal_error
Esempio n. 8
0
def execute_query(connstr):
    excel_out_file = ''       
    keeplooping = 'yes'
    while keeplooping == 'yes':


        sqlstr = input("Enter SQL String to be Executed:")
        #Get a new instance of the class with the updated sql string
        myquery = DBQuery(connstr, sqlstr)
        
        #If the SQL represents a SELECT statement, return data as a pandas dataframe and write to Excel file
        if sqlstr.upper().startswith('SELECT'):
            df = pd.DataFrame()
            try:
                df = myquery.get_dataframe()
                if df.empty == False:
                    print('Dataframe was created Successfully.')
                    excel_out_file = input('Enter Excel Output File Path:')
                    try:
                        df.to_excel(excel_out_file)
                        print('Dataframe Successfully Written to Excel File: ' + excel_out_file)
                    except Exception as err:
                        print('Dataframe was NOT Successfully Written to File.')
                        print(err)
                else:
                    print('Dataframe Creation was Not Successful or Returned No Results.')
                
            except Exception as err:
                print('Dataframe Creation was NOT Successful.')
                #print(err)
                #keeplooping = 'no'
                        
        else:   #execute cursor              
            try:
                myquery.exe_cursor()
                print('SQL Statement was Successfully Executed.')
            except Exception as err:
                print('SQL Statement was Not Successfully Executed.')
                print(err)
              
        #See if the user wants to execute more SQL Statements
        exitstatus = input('Execute Another SQL Statement? ("yes" or "no":) ')
        if exitstatus.lower() != 'yes' and exitstatus != 'y':
            keeplooping = 'no'
            print('Ending Program')
Esempio n. 9
0
    def __init__(self, env_cls, args, manager, cfg, process_num, pre=False, pre_irl=False, infer=False, realtrain=False):
        """
        :param env_cls: env class or function, not instance, as we need to create several instance in class.
        :param args:
        :param manager:
        :param cfg:
        :param process_num: process number
        :param pre: set to pretrain mode
        :param infer: set to test mode
        """
        self.train_direc = args.train_direc
        self.realtrain = realtrain
        self.process_num = process_num
        self.human_reward = human_reward(args, cfg)
        # initialize envs for each process
        self.env_list = []
        for _ in range(process_num):
            self.env_list.append(env_cls())

        # construct policy and value network
        self.feature_layer = nn.Sequential(
                                 nn.Linear(cfg.s_dim, cfg.h_dim),
                                 nn.ReLU()
        )
        self.policy = MultiDiscretePolicy(cfg, self.feature_layer).to(device=DEVICE)
        self.value = Value(cfg).to(device=DEVICE)
        self.multi_entropy_loss = nn.MultiLabelSoftMarginLoss()

        
        if pre:
            self.print_per_batch = args.print_per_batch
            from dbquery import DBQuery
            db = DBQuery(args.data_dir)
            self.data_train = manager.create_dataset_rl('train', args.batchsz, cfg, db)
            self.data_valid = manager.create_dataset_rl('valid', args.batchsz, cfg, db)
            self.data_test = manager.create_dataset_rl('test', args.batchsz, cfg, db)
            # self.multi_entropy_loss = nn.MultiLabelSoftMarginLoss()
        else:
            self.rewarder = RewardEstimator(args, manager, cfg, pretrain=pre_irl, inference=infer, feature_extractor=self.policy)
            # self.rewarder = DiscEstimator(args, manager, cfg, pretrain=pre_irl, inference=infer)
            self.evaluator = Evaluator(args.data_dir, cfg)

        self.save_dir = args.save_dir
        self.save_per_epoch = args.save_per_epoch
        self.optim_batchsz = args.batchsz
        self.update_round = args.update_round
        self.policy.eval()
        self.value.eval()
        
        self.gamma = args.gamma
        self.epsilon = args.epsilon
        self.tau = args.tau
        self.policy_optim = optim.RMSprop(self.policy.parameters(), lr=args.lr_rl)
        self.value_optim = optim.Adam(self.value.parameters(), lr=args.lr_rl)
        self.mt_factor = args.mt_factor
Esempio n. 10
0
 def __init__(self, data_dir, domain=None):
     self.sys_da_array = []
     self.usr_da_array = []
     self.goal = {}
     self.booked = {}
     self.cur_domain = ''
     self.complete_domain = []
     from config import MultiWozConfig
     cfg = MultiWozConfig(domain)
     self.belief_domains = cfg.belief_domains
     self.mapping = cfg.mapping
     db = DBQuery(data_dir, cfg)
     self.dbs = db.dbs
Esempio n. 11
0
    def __init__(self,
                 args,
                 manager,
                 config,
                 pretrain=False,
                 inference=False,
                 feature_extractor=None):
        self.feature_extractor = feature_extractor
        if args.irl_net == 'AIRL_MT':
            self.irl = AIRL_MT(config, args.gamma).to(device=DEVICE)
        elif args.irl_net == 'AIRL_MTSA':
            self.irl = AIRL_MTSA(config, args.gamma).to(device=DEVICE)
        else:
            self.irl = AIRL_ORI(config, args.gamma).to(device=DEVICE)
        self.net_type = args.irl_net
        self.train_direc = args.train_direc
        self.mt_factor = args.mt_factor
        self.bce_loss = nn.BCEWithLogitsLoss()
        self.multi_entropy_loss = nn.MultiLabelSoftMarginLoss()

        self.step = 0
        self.anneal = args.anneal
        self.irl_params = self.irl.parameters()
        self.irl_optim = optim.RMSprop(self.irl_params, lr=args.lr_irl)
        self.weight_cliping_limit = args.clip

        self.save_dir = args.save_dir
        self.save_per_epoch = args.save_per_epoch
        self.optim_batchsz = args.batchsz
        self.irl.eval()

        db = DBQuery(args.data_dir)
        if pretrain:
            self.print_per_batch = args.print_per_batch
            self.data_train = manager.create_dataset_irl(
                'train', args.batchsz, config, db)
            self.data_valid = manager.create_dataset_irl(
                'valid', args.batchsz, config, db)
            self.data_test = manager.create_dataset_irl(
                'test', args.batchsz, config, db)
            self.irl_iter = iter(self.data_train)
            self.irl_iter_valid = iter(self.data_valid)
            self.irl_iter_test = iter(self.data_test)
        elif not inference:
            self.data_train = manager.create_dataset_irl(
                'train', args.batchsz, config, db)
            self.data_valid = manager.create_dataset_irl(
                'valid', args.batchsz, config, db)
            self.irl_iter = iter(self.data_train)
            self.irl_iter_valid = iter(self.data_valid)
Esempio n. 12
0
    def __init__(self, data_dir, cfg):
        self.data = {}
        self.goal = {}

        self.data_dir_new = data_dir + '/processed_data'
        if os.path.exists(self.data_dir_new):
            logging.info('Load processed data file')
            for part in ['train', 'valid', 'test']:
                with open(self.data_dir_new + '/' + part + '.json', 'r') as f:
                    self.data[part] = json.load(f)
                with open(self.data_dir_new + '/' + part + '_goal.json',
                          'r') as f:
                    self.goal[part] = json.load(f)
        else:
            from dbquery import DBQuery
            db = DBQuery(data_dir, cfg)
            logging.info('Start preprocessing the dataset')
            self._build_data(data_dir, self.data_dir_new, cfg, db)

        for part in ['train', 'valid', 'test']:
            file_dir = self.data_dir_new + '/' + part + '_sys.pt'
            if not os.path.exists(file_dir):
                from dbquery import DBQuery
                db = DBQuery(data_dir, cfg)
                self.create_dataset_sys(part, file_dir, data_dir, cfg, db)

            file_dir = self.data_dir_new + '/' + part + '_usr.pt'
            if not os.path.exists(file_dir):
                from dbquery import DBQuery
                db = DBQuery(data_dir, cfg)
                self.create_dataset_usr(part, file_dir, data_dir, cfg, db)

            file_dir = self.data_dir_new + '/' + part + '_glo.pt'
            if not os.path.exists(file_dir):
                from dbquery import DBQuery
                db = DBQuery(data_dir, cfg)
                self.create_dataset_global(part, file_dir, data_dir, cfg, db)
Esempio n. 13
0
    def __init__(self, env_cls, args, manager, cfg, process_num, character, pre=False, infer=False):
        """
        专门用于更新预训练模型
        :param env_cls: env class or function, not instance, as we need to create several instance in class.
        :param args:
        :param manager:
        :param cfg:
        :param process_num: process number
        :param character: user or system
        :param pre: set to pretrain mode
        :param infer: set to test mode
        """

        self.process_num = process_num
        self.character = character

        # initialize envs for each process
        self.env_list = []
        for _ in range(process_num):
            self.env_list.append(env_cls())

        # construct policy and value network
        self.policy = MultiDiscretePolicy(cfg, character).to(device=DEVICE)

        if pre:
            self.print_per_batch = args.print_per_batch
            from dbquery import DBQuery
            db = DBQuery(args.data_dir, cfg)
            self.data_train = manager.create_dataset_policy('train', args.batchsz, cfg, db, character)
            self.data_valid = manager.create_dataset_policy('valid', args.batchsz, cfg, db, character)
            self.data_test = manager.create_dataset_policy('test', args.batchsz, cfg, db, character)
            if character == 'sys':
                pos_weight = args.policy_weight_sys * torch.ones([cfg.a_dim]).to(device=DEVICE)
            elif character == 'usr':
                pos_weight = args.policy_weight_usr * torch.ones([cfg.a_dim_usr]).to(device=DEVICE)
            else:
                raise Exception('Unknown character')
            self.multi_entropy_loss = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
        else:
            self.evaluator = MultiWozEvaluator(args.data_dir, cfg.d)

        self.save_dir = args.save_dir + '/' + character if pre else args.save_dir
        self.save_per_epoch = args.save_per_epoch
        self.optim_batchsz = args.batchsz
        self.policy.eval()

        self.gamma = args.gamma
        self.policy_optim = optim.RMSprop(self.policy.parameters(), lr=args.lr_policy, weight_decay=args.weight_decay)
        self.writer = SummaryWriter()
Esempio n. 14
0
    def __init__(self, args, manager, config, pretrain=False, inference=False):
        self.disc_type = args.gan_type
        if self.disc_type == 'vanilla':
            self.irl = DISC_Vanilla(config, args.gamma).to(device=DEVICE)
        elif self.disc_type == 'wgan':
            self.irl = DISC_MTSA(config, args.gamma).to(device=DEVICE)
        else:
            raise ValueError(" No such type: {}".format(self.disc_type))
        logging.info(summary(self.irl, show_weights=False))
        self.net_type = args.irl_net
        self.train_direc = args.train_direc
        self.mt_factor = args.mt_factor
        self.action_lambda = 0
        self.bce_loss = nn.BCEWithLogitsLoss()
        self.multi_entropy_loss = nn.MultiLabelSoftMarginLoss()
        self.loss_BCE = nn.BCELoss()

        self.step = 0
        self.anneal = args.anneal
        self.irl_params = self.irl.parameters()
        self.irl_optim = optim.Adam(self.irl_params, lr=args.lr_irl)
        self.weight_cliping_limit = args.clip

        self.save_dir = args.save_dir
        self.save_per_epoch = args.save_per_epoch
        self.optim_batchsz = args.batchsz
        self.irl.eval()

        db = DBQuery(args.data_dir)
        if pretrain:
            self.print_per_batch = args.print_per_batch
            self.data_train = manager.create_dataset_irl(
                'train', args.batchsz, config, db)
            self.data_valid = manager.create_dataset_irl(
                'valid', args.batchsz, config, db)
            self.data_test = manager.create_dataset_irl(
                'test', args.batchsz, config, db)
            self.irl_iter = iter(self.data_train)
            self.irl_iter_valid = iter(self.data_valid)
            self.irl_iter_test = iter(self.data_test)
        elif not inference:
            self.data_train = manager.create_dataset_irl(
                'train', args.batchsz, config, db)
            self.data_valid = manager.create_dataset_irl(
                'valid', args.batchsz, config, db)
            self.irl_iter = iter(self.data_train)
            self.irl_iter_valid = iter(self.data_valid)
Esempio n. 15
0
    def __init__(self, data_dir, cfg):
        self.max_turn = 40
        self.max_initiative = 4
        self.cfg = cfg
        self.db = DBQuery(data_dir)
        
        # load stand value
        with open(data_dir + '/' + cfg.ontology_file) as f:
            self.stand_value_dict = json.load(f)

        self.goal_generator = GoalGenerator(data_dir,
                                            goal_model_path='processed_data/goal_model.pkl',
                                            corpus_path=cfg.data_file)

        self.time_step = 0
        self.goal = None
        self.agenda = None
Esempio n. 16
0
class StateTracker(object):
    def __init__(self, data_dir, config):
        self.time_step = 0
        self.cfg = config
        self.db = DBQuery(data_dir)
        self.topic = 'NONE'

    def _action_to_dict(self, das):
        da_dict = {}
        for da, value in das.items():
            domain, intent, slot, p = da.split('-')
            domint = '-'.join((domain, intent))
            if domint not in da_dict:
                da_dict[domint] = []
            da_dict[domint].append([slot, value])
        return da_dict

    def _dict_to_vec(self, das):
        da_vector = torch.zeros(self.cfg.a_dim_usr, dtype=torch.int32)
        expand_da(das)
        for domint in das:
            pairs = das[domint]
            for slot, p, value in pairs:
                da = '-'.join((domint, slot, p)).lower()
                if da in self.cfg.dau2idx:
                    idx = self.cfg.dau2idx[da]
                    da_vector[idx] = 1
        return da_vector

    def _mask_user_goal(self, goal):
        domain_ordering = list(goal['domain_ordering'])
        if 'hospital' in goal:
            del (goal['hospital'])
            domain_ordering.remove('hospital')
        if 'police' in goal:
            del (goal['police'])
            domain_ordering.remove('police')
        goal['domain_ordering'] = tuple(domain_ordering)

    def get_entities(self, s, domain):
        origin = s['belief_state']['inform'][domain].items()
        constraint = []
        for k, v in origin:
            if k in self.cfg.mapping[domain]:
                constraint.append((self.cfg.mapping[domain][k], v))
        entities = self.db.query(domain, constraint)
        random.shuffle(entities)
        return entities

    def update_belief_sys(self, old_s, a):
        """
        update belief state with sys action
        """
        s = deepcopy(old_s)
        a_index = torch.nonzero(a)  # get multiple da indices

        self.time_step += 1
        s['others']['turn'] = self.time_step

        # update sys/user dialog act
        s['history']['sys'] = dict(s['history']['sys'], **s['last_sys_action'])
        del (s['last_sys_action'])
        s['last_user_action'] = s['user_action']
        s['user_action'] = dict()

        # update belief part
        das = [self.cfg.idx2da[idx.item()] for idx in a_index]
        das = [da.split('-') for da in das]
        sorted(das, key=lambda x: x[0])  # sort by domain

        entities = [] if self.topic == 'NONE' else self.get_entities(
            s, self.topic)
        for domain, intent, slot, p in das:
            _domain = self.topic if domain == 'booking' else domain
            if domain in self.cfg.belief_domains and domain != self.topic:
                self.topic = domain
                entities = self.get_entities(s, domain)

            da = '-'.join((domain, intent, slot, p))
            if p == 'none':
                s['sys_action'][da] = 'none'
            elif p == '?':
                s['sys_action'][da] = '?'
            elif intent in ['nooffer', 'nobook']:
                if slot in s['belief_state']['inform'][_domain]:
                    s['sys_action'][da] = s['belief_state']['inform'][_domain][
                        slot]
                else:
                    s['sys_action'][da] = 'none'
            elif slot == 'choice':
                s['sys_action'][da] = str(len(entities))
            else:
                num = int(p) - 1
                if len(entities) > num and slot in self.cfg.mapping[_domain]:
                    typ = self.cfg.mapping[_domain][slot]
                    s['sys_action'][da] = entities[num][typ]
                else:
                    s['sys_action'][da] = 'none'

            if intent == 'inform' and _domain != 'NONE':
                s['belief_state']['request'][_domain].discard(slot)

            # booked
            if intent == 'inform' and slot == 'car':  # taxi
                if not s['belief_state']['booked']['taxi']:
                    s['belief_state']['booked']['taxi'] == 'booked'
            elif intent == 'offerbooked' and slot == 'ref':  # train
                s['belief_state']['request']['train'].discard('ref')
                if not s['belief_state']['booked']['train'] and entities:
                    s['belief_state']['booked']['train'] = entities[0]['ref']
            elif intent == 'book' and slot == 'ref':  # attraction, hotel, restaurant
                if _domain not in ['attraction', 'hotel', 'restaurant']:
                    continue
                s['belief_state']['request'][_domain].discard('ref')
                if not s['belief_state']['booked'][_domain] and entities:
                    # save entity id
                    s['belief_state']['booked'][_domain] = entities[0]['ref']

        return s

    def update_belief_usr(self, old_s, a, terminal):
        """
        update belief state with user action
        """
        s = deepcopy(old_s)
        a_index = torch.nonzero(a)  # get multiple da indices

        self.time_step += 1
        s['others']['turn'] = self.time_step
        s['others']['terminal'] = terminal

        # update sys/user dialog act
        s['history']['user'] = dict(s['history']['user'],
                                    **s['last_user_action'])
        del (s['last_user_action'])
        s['last_sys_action'] = s['sys_action']
        s['sys_action'] = dict()

        # update belief part
        das = [self.cfg.idx2dau[idx.item()] for idx in a_index]
        das = [da.split('-') for da in das]
        sorted(das, key=lambda x: x[0])  # sort by domain

        for domain, intent, slot, p in das:
            if domain in self.cfg.belief_domains and domain != self.topic:
                self.topic = domain

            da = '-'.join((domain, intent, slot, p))
            if p == 'none':
                s['user_action'][da] = 'none'
            elif p == '?':
                s['user_action'][da] = '?'
            else:
                if slot in s['user_goal']['inform'][domain]:
                    s['user_action'][da] = s['user_goal']['inform'][domain][
                        slot]
                else:
                    s['user_action'][da] = 'none'

            if slot != 'none':
                if intent == 'inform':
                    # update constraints with reasonable value according to user goal
                    if slot in s['user_goal']['inform'][domain]:
                        s['belief_state']['inform'][domain][slot] = s[
                            'user_goal']['inform'][domain][slot]  # value
                    else:
                        s['belief_state']['inform'][domain][slot] = 'none'

                elif intent == 'request':
                    s['belief_state']['request'][domain].add(slot)

        return s

    def reset(self, random_seed=None):
        """
        Args:
            random_seed (int):
        Returns:
            init_state (dict):
        """
        pass

    def step(self, s, sys_a):
        """
        Args:
            s (dict):
            sys_a (vector):
        Returns:
            next_s (dict):
            terminal (bool):
        """
        pass
Esempio n. 17
0
 def __init__(self, data_dir, cfg):
     self.db = DBQuery(data_dir)
     self.cfg = cfg
Esempio n. 18
0
class GoalGenerator:
    """User goal generator"""
    def __init__(self,
                 data_dir,
                 cfg,
                 goal_model_path,
                 corpus_path=None,
                 boldify=False):
        """
        生成各种分布:
        cfg 这里只用于为dbquery提供地址,并没有提供对数据分布进行控制
        self.ind_slot_dist: 独立实体分布
        self.ind_slot_value_dist: 独立实体值分布
        self.domain_ordering_dist: domain请求分布
        self.book_dist: 订阅分布
        Args:
            goal_model_path: path to a goal model
            corpus_path: path to a dialog corpus to build a goal model
        """
        self.cfg = cfg
        if self.cfg.d:
            self.domains = [self.cfg.d]
        else:
            print(self.cfg)
            self.domains = {
                'attraction', 'hotel', 'restaurant', 'train', 'taxi',
                'hospital', 'police'
            }
        self.dbquery = DBQuery(data_dir, cfg)
        self.goal_model_path = data_dir + '/' + goal_model_path
        self.corpus_path = data_dir + '/' + corpus_path if corpus_path is not None else None
        self.boldify = do_boldify if boldify else null_boldify
        if os.path.exists(self.goal_model_path):
            with open(self.goal_model_path, 'rb') as f:
                self.ind_slot_dist, self.ind_slot_value_dist, self.domain_ordering_dist, self.book_dist = pickle.load(
                    f)
            logging.info('Loading goal model is done')
        else:
            self._build_goal_model()
            logging.info('Building goal model is done')

        # remove some slot
        if 'police' in self.ind_slot_dist:
            del self.ind_slot_dist['police']['reqt']['postcode']
            del self.ind_slot_value_dist['police']['reqt']['postcode']
        if 'hospital' in self.ind_slot_dist:
            del self.ind_slot_dist['hospital']['reqt']['postcode']
            del self.ind_slot_value_dist['hospital']['reqt']['postcode']
            del self.ind_slot_dist['hospital']['reqt']['address']
            del self.ind_slot_value_dist['hospital']['reqt']['address']

    def _build_goal_model(self):
        with open(self.corpus_path) as f:
            dialogs = json.load(f)

        # domain ordering
        def _get_dialog_domains(dialog):
            """收集dialog中的有效domains"""
            return list(
                filter(
                    lambda x: x in self.domains and len(dialog['goal'][x]) > 0,
                    dialog['goal']))

        domain_orderings = []
        for d in dialogs:
            d_domains = _get_dialog_domains(dialogs[d])

            if self.cfg.d:
                # 直接跳过多场景切换问题, 如果需要增加对多场景切换问题的研究,则需要注释掉这段
                if len(d_domains) > 1 or len(d_domains) == 0:
                    continue

            # print("d_domains: ", d_domains)

            first_index = []
            # 找到每个domain的首行message
            for domain in d_domains:
                message = [dialogs[d]['goal']['message']] if type(dialogs[d]['goal']['message']) == str else \
                    dialogs[d]['goal']['message']
                for i, m in enumerate(message):
                    if domain_keywords[domain].lower() in m.lower(
                    ) or domain.lower() in m.lower():
                        first_index.append(i)
                        break
            # 根据首行message的实际序号,调整domains的出现顺序
            domain_orderings.append(
                tuple(
                    map(
                        lambda x: x[1],
                        sorted(zip(first_index, d_domains),
                               key=lambda x: x[0]))))
        domain_ordering_cnt = Counter(domain_orderings)
        self.domain_ordering_dist = deepcopy(domain_ordering_cnt)
        # 转化为概率值
        for order in domain_ordering_cnt.keys():
            self.domain_ordering_dist[order] = domain_ordering_cnt[
                order] / sum(domain_ordering_cnt.values())

        # independent goal slot distribution
        ind_slot_value_cnt = dict([(domain, {}) for domain in self.domains])
        domain_cnt = Counter()
        book_cnt = Counter()

        # 对各个用户的用户目标进行统计
        for d in dialogs:
            for domain in self.domains:
                # 统计domain出现的次数
                if dialogs[d]['goal'][domain] != {}:
                    domain_cnt[domain] += 1
                # 统计info中各个slot及其slot-value出现的次数
                if 'info' in dialogs[d]['goal'][domain]:
                    for slot in dialogs[d]['goal'][domain]['info']:
                        if 'invalid' in slot:
                            continue
                        if 'info' not in ind_slot_value_cnt[domain]:
                            ind_slot_value_cnt[domain]['info'] = {}
                        if slot not in ind_slot_value_cnt[domain]['info']:
                            ind_slot_value_cnt[domain]['info'][slot] = Counter(
                            )
                        if 'care' in dialogs[d]['goal'][domain]['info'][slot]:
                            continue
                        slot_value = dialogs[d]['goal'][domain]['info'][slot]
                        ind_slot_value_cnt[domain]['info'][slot][
                            slot_value] += 1
                # 统计用户目标汇总reqt的slot出现的次数,由于没有value,因此直接对slot进行统计
                if 'reqt' in dialogs[d]['goal'][domain]:
                    for slot in dialogs[d]['goal'][domain]['reqt']:
                        if 'reqt' not in ind_slot_value_cnt[domain]:
                            ind_slot_value_cnt[domain]['reqt'] = Counter()
                        ind_slot_value_cnt[domain]['reqt'][slot] += 1
                # 统计用户目标中book的信息
                if 'book' in dialogs[d]['goal'][domain]:
                    # 各个domain book的概率
                    book_cnt[domain] += 1
                    # book中限制slot的value的次数
                    for slot in dialogs[d]['goal'][domain]['book']:
                        if 'invalid' in slot:
                            continue
                        if 'book' not in ind_slot_value_cnt[domain]:
                            ind_slot_value_cnt[domain]['book'] = {}
                        if slot not in ind_slot_value_cnt[domain]['book']:
                            ind_slot_value_cnt[domain]['book'][slot] = Counter(
                            )
                        if 'care' in dialogs[d]['goal'][domain]['book'][slot]:
                            continue
                        ind_slot_value_cnt[domain]['book'][slot][
                            dialogs[d]['goal'][domain]['book'][slot]] += 1
        # 收集完成ind_slot_value_cnt 和 domain_cnt 和 book_cnt 的信息
        # 计算得到 ind_slot_dist ind_slot_value_dist 基于不同domain的分布
        self.ind_slot_value_dist = deepcopy(ind_slot_value_cnt)
        self.ind_slot_dist = dict([(domain, {}) for domain in self.domains])
        self.book_dist = {}
        for domain in self.domains:
            if 'info' in ind_slot_value_cnt[domain]:
                for slot in ind_slot_value_cnt[domain]['info']:
                    if 'info' not in self.ind_slot_dist[domain]:
                        self.ind_slot_dist[domain]['info'] = {}
                    if slot not in self.ind_slot_dist[domain]['info']:
                        self.ind_slot_dist[domain]['info'][slot] = {}
                    self.ind_slot_dist[domain]['info'][slot] = sum(ind_slot_value_cnt[domain]['info'][slot].values()) / \
                                                               domain_cnt[domain]
                    slot_total = sum(
                        ind_slot_value_cnt[domain]['info'][slot].values())
                    for val in self.ind_slot_value_dist[domain]['info'][slot]:
                        self.ind_slot_value_dist[domain]['info'][slot][
                            val] = ind_slot_value_cnt[domain]['info'][slot][
                                val] / slot_total
            if 'reqt' in ind_slot_value_cnt[domain]:
                for slot in ind_slot_value_cnt[domain]['reqt']:
                    if 'reqt' not in self.ind_slot_dist[domain]:
                        self.ind_slot_dist[domain]['reqt'] = {}
                    self.ind_slot_dist[domain]['reqt'][
                        slot] = ind_slot_value_cnt[domain]['reqt'][
                            slot] / domain_cnt[domain]
                    self.ind_slot_value_dist[domain]['reqt'][slot] = ind_slot_value_cnt[domain]['reqt'][slot] / \
                                                                     domain_cnt[domain]
            if 'book' in ind_slot_value_cnt[domain]:
                for slot in ind_slot_value_cnt[domain]['book']:
                    if 'book' not in self.ind_slot_dist[domain]:
                        self.ind_slot_dist[domain]['book'] = {}
                    if slot not in self.ind_slot_dist[domain]['book']:
                        self.ind_slot_dist[domain]['book'][slot] = {}
                    self.ind_slot_dist[domain]['book'][slot] = sum(ind_slot_value_cnt[domain]['book'][slot].values()) / \
                                                               domain_cnt[domain]
                    slot_total = sum(
                        ind_slot_value_cnt[domain]['book'][slot].values())
                    for val in self.ind_slot_value_dist[domain]['book'][slot]:
                        self.ind_slot_value_dist[domain]['book'][slot][
                            val] = ind_slot_value_cnt[domain]['book'][slot][
                                val] / slot_total
            self.book_dist[domain] = book_cnt[domain] / len(dialogs)

        # print()
        # print("ind_slot_dist:", self.ind_slot_dist)
        # print("ind_slot_value_dist ", self.ind_slot_value_dist)
        # print("domain_ordering_dist:", self.domain_ordering_dist)
        # print("book_dist:", self.book_dist)
        # print()

        goal_model_path_dir = os.path.dirname(self.goal_model_path)
        if len(goal_model_path_dir) > 0 and not os.path.exists(
                goal_model_path_dir):
            os.makedirs(goal_model_path_dir)

        with open(self.goal_model_path, 'wb') as f:
            pickle.dump((self.ind_slot_dist, self.ind_slot_value_dist,
                         self.domain_ordering_dist, self.book_dist), f)

    def _get_domain_goal(self, domain):
        """按照domain的设定,生成对应的目标"""
        cnt_slot = self.ind_slot_dist[domain]
        cnt_slot_value = self.ind_slot_value_dist[domain]
        pro_book = self.book_dist[domain]

        while True:
            domain_goal = {'info': {}}
            # inform
            if 'info' in cnt_slot:
                for slot in cnt_slot['info']:
                    # 一定概率增加对该slot的限制
                    if random.random(
                    ) < cnt_slot['info'][slot] + pro_correction['info']:
                        domain_goal['info'][slot] = nomial_sample(
                            cnt_slot_value['info'][slot])
                # 对hotel restaurant attraction的限制,
                if domain in ['hotel', 'restaurant', 'attraction'
                              ] and 'name' in domain_goal['info'] and len(
                                  domain_goal['info']) > 1:
                    # 一定概率提供name,但是只需要提供name就可以了,其他的信息已经不重要了
                    if random.random() < cnt_slot['info']['name']:
                        domain_goal['info'] = {
                            'name': domain_goal['info']['name']
                        }
                    else:
                        # 或者删掉name
                        del domain_goal['info']['name']

                # 对于taxi 和train, 采用一定的概率删除leaveAt和arriveBy中的一个,不然答案就唯一了
                if domain in ['taxi', 'train'] and 'arriveBy' in domain_goal[
                        'info'] and 'leaveAt' in domain_goal['info']:
                    if random.random() < (cnt_slot['info']['leaveAt'] /
                                          (cnt_slot['info']['arriveBy'] +
                                           cnt_slot['info']['leaveAt'])):
                        del domain_goal['info']['arriveBy']
                    else:
                        del domain_goal['info']['leaveAt']

                # 但是arriveBy和leaveAt又必须提供一个
                if domain in ['taxi', 'train'] and 'arriveBy' not in domain_goal['info'] and 'leaveAt' not in \
                        domain_goal['info']:
                    if random.random() < (cnt_slot['info']['arriveBy'] /
                                          (cnt_slot['info']['arriveBy'] +
                                           cnt_slot['info']['leaveAt'])):
                        domain_goal['info']['arriveBy'] = nomial_sample(
                            cnt_slot_value['info']['arriveBy'])
                    else:
                        domain_goal['info']['leaveAt'] = nomial_sample(
                            cnt_slot_value['info']['leaveAt'])

                # 需要告知出发地点和目的地
                if domain in ['taxi', 'train'
                              ] and 'departure' not in domain_goal['info']:
                    domain_goal['info']['departure'] = nomial_sample(
                        cnt_slot_value['info']['departure'])
                if domain in ['taxi', 'train'
                              ] and 'destination' not in domain_goal['info']:
                    domain_goal['info']['destination'] = nomial_sample(
                        cnt_slot_value['info']['destination'])

                # 如果出发地和目的地一样,一定概率重新采样
                if domain in ['taxi', 'train'] and \
                        'departure' in domain_goal['info'] and \
                        'destination' in domain_goal['info']:
                    while domain_goal['info']['departure'] == domain_goal[
                            'info']['destination']:
                        if random.random() < (
                                cnt_slot['info']['departure'] /
                            (cnt_slot['info']['departure'] +
                             cnt_slot['info']['destination'])):
                            domain_goal['info']['departure'] = nomial_sample(
                                cnt_slot_value['info']['departure'])
                        else:
                            domain_goal['info']['destination'] = nomial_sample(
                                cnt_slot_value['info']['destination'])
#                        print("same destination departure")
                if domain_goal['info'] == {}:
                    # 如果没有用户提供的信息,则重新生成
                    continue
            # request
            # 针对infor的设定,随机选择request的限制
            if 'reqt' in cnt_slot:
                reqt = [
                    slot for slot in cnt_slot['reqt']
                    if random.random() < cnt_slot['reqt'][slot] +
                    pro_correction['reqt'] and slot not in domain_goal['info']
                ]
                if len(reqt) > 0:
                    domain_goal['reqt'] = reqt

            # book
            # 一定概率决定是否book, 但是对于book的信息并没有和info生成的信息进行协调
            if 'book' in cnt_slot and random.random(
            ) < pro_book + pro_correction['book']:
                if 'book' not in domain_goal:
                    domain_goal['book'] = {}

                for slot in cnt_slot['book']:
                    # 随机选取book的限制
                    if random.random(
                    ) < cnt_slot['book'][slot] + pro_correction['book']:
                        domain_goal['book'][slot] = nomial_sample(
                            cnt_slot_value['book'][slot])

                # makes sure that there are all necessary slots for booking
                # 预定餐馆需要告知时间
                if domain == 'restaurant' and 'time' not in domain_goal['book']:
                    domain_goal['book']['time'] = nomial_sample(
                        cnt_slot_value['book']['time'])
                # 预定旅馆需要告知几天
                if domain == 'hotel' and 'stay' not in domain_goal['book']:
                    domain_goal['book']['stay'] = nomial_sample(
                        cnt_slot_value['book']['stay'])
                # 预定残旅馆需要告知哪天几个人
                if domain in ['hotel', 'restaurant'
                              ] and 'day' not in domain_goal['book']:
                    domain_goal['book']['day'] = nomial_sample(
                        cnt_slot_value['book']['day'])
                if domain in ['hotel', 'restaurant'
                              ] and 'people' not in domain_goal['book']:
                    domain_goal['book']['people'] = nomial_sample(
                        cnt_slot_value['book']['people'])

                # 如果是train,需要告知几个人
                if domain == 'train' and len(domain_goal['book']) <= 0:
                    domain_goal['book']['people'] = nomial_sample(
                        cnt_slot_value['book']['people'])

            # fail_book
            # 只有残旅馆会出现fail_book
            if 'book' in domain_goal and random.random() < 0.5:
                # 对于旅馆的订购失败设定
                if domain == 'hotel':
                    domain_goal['fail_book'] = deepcopy(domain_goal['book'])
                    if 'stay' in domain_goal['book'] and random.random() < 0.5:
                        # increase hotel-stay
                        # 一定概率增加天数
                        domain_goal['fail_book']['stay'] = str(
                            int(domain_goal['book']['stay']) + 1)
                    elif 'day' in domain_goal['book']:
                        # push back hotel-day by a day
                        # 一定概率提前日期
                        domain_goal['fail_book']['day'] = days[
                            (days.index(domain_goal['book']['day']) - 1) % 7]

                elif domain == 'restaurant':
                    domain_goal['fail_book'] = deepcopy(domain_goal['book'])
                    if 'time' in domain_goal['book'] and random.random() < 0.5:
                        hour, minute = domain_goal['book']['time'].split(':')
                        # 增加1小时
                        domain_goal['fail_book']['time'] = str(
                            (int(hour) + 1) % 24) + ':' + minute
                    elif 'day' in domain_goal['book']:
                        if random.random() < 0.5:
                            # 提前一天
                            domain_goal['fail_book']['day'] = days[
                                (days.index(domain_goal['book']['day']) - 1) %
                                7]
                        else:
                            # 推迟一天
                            domain_goal['fail_book']['day'] = days[
                                (days.index(domain_goal['book']['day']) + 1) %
                                7]

            # fail_info
            if 'info' in domain_goal and len(
                    self.dbquery.query(domain,
                                       domain_goal['info'].items())) == 0:
                # 随机生成的信息查询不到时,设置fail_info
                num_trial = 0
                # 尝试重新生成,尝试次数不超过100次
                while num_trial < 100:
                    adjusted_info = self._adjust_info(domain,
                                                      domain_goal['info'])
                    if len(self.dbquery.query(domain,
                                              adjusted_info.items())) > 0:
                        # train不经行fail_info
                        if domain == 'train':
                            domain_goal['info'] = adjusted_info
                        else:
                            domain_goal['fail_info'] = domain_goal['info']
                            domain_goal['info'] = adjusted_info

                        break
                    num_trial += 1

                if num_trial >= 100:
                    # 大于100次尝试失败,重新生成用户目标
                    continue

            # at least there is one request and book
            # 用户至少需要提一个要求
            if 'reqt' in domain_goal or 'book' in domain_goal:
                break

        return domain_goal

    def get_user_goal(self, seed=None):
        # seed the generator to get fixed goal
        if seed is not None:
            random.seed(seed)
            np.random.seed(seed)

        # 先确定domain_ordering
        domain_ordering = []
        while not domain_ordering:
            domain_ordering = list(nomial_sample(self.domain_ordering_dist))
        np.random.shuffle(domain_ordering)

        user_goal = {
            dom: self._get_domain_goal(dom)
            for dom in domain_ordering
        }
        assert len(user_goal.keys()) > 0

        # using taxi to communte between places, removing destination and departure.
        # 如果taxi中有地址出现,则需要跟之前的domain一致,
        if 'taxi' in domain_ordering:
            places = [
                dom for dom in domain_ordering[:domain_ordering.index('taxi')]
                if 'address' in self.ind_slot_dist[dom]['reqt'].keys()
            ]
            if len(places) >= 1:
                del user_goal['taxi']['info']['destination']
                user_goal[places[-1]]['reqt'] = list(
                    set(user_goal[places[-1]].get('reqt',
                                                  [])).union({'address'}))
                # 时间上也要保持一致
                if places[-1] == 'restaurant' and 'book' in user_goal[
                        'restaurant']:
                    user_goal['taxi']['info']['arriveBy'] = user_goal[
                        'restaurant']['book']['time']
                    if 'leaveAt' in user_goal['taxi']['info']:
                        del user_goal['taxi']['info']['leaveAt']
            # 起点和终点
            if len(places) >= 2:
                del user_goal['taxi']['info']['departure']
                user_goal[places[-2]]['reqt'] = list(
                    set(user_goal[places[-2]].get('reqt',
                                                  [])).union({'address'}))

        # match area of attraction and restaurant
        # 景点和饭店地点应该在一起
        if 'restaurant' in domain_ordering and \
                'attraction' in domain_ordering and \
                'fail_info' not in user_goal['restaurant'] and \
                domain_ordering.index('restaurant') > domain_ordering.index('attraction') and \
                'area' in user_goal['restaurant']['info'] and 'area' in user_goal['attraction']['info']:
            adjusted_restaurant_goal = deepcopy(
                user_goal['restaurant']['info'])
            adjusted_restaurant_goal['area'] = user_goal['attraction']['info'][
                'area']
            # 数据库有的话,一定程度转移
            if len(
                    self.dbquery.query('restaurant',
                                       adjusted_restaurant_goal.items())
            ) > 0 and random.random() < 0.5:
                user_goal['restaurant']['info']['area'] = user_goal[
                    'attraction']['info']['area']

        # match day and people of restaurant and hotel
        # 餐旅馆的人数和天数应该一致,调整之后检查是不是和fail_book冲突了,如果是删掉fail_book
        if 'restaurant' in domain_ordering and 'hotel' in domain_ordering and \
                'book' in user_goal['restaurant'] and 'book' in user_goal['hotel']:
            if random.random() < 0.5:
                user_goal['restaurant']['book']['people'] = user_goal['hotel'][
                    'book']['people']
                if 'fail_book' in user_goal['restaurant']:
                    user_goal['restaurant']['fail_book']['people'] = user_goal[
                        'hotel']['book']['people']
            if random.random() < 1.0:
                user_goal['restaurant']['book']['day'] = user_goal['hotel'][
                    'book']['day']
                if 'fail_book' in user_goal['restaurant']:
                    user_goal['restaurant']['fail_book']['day'] = user_goal[
                        'hotel']['book']['day']
                    if user_goal['restaurant']['book']['day'] == user_goal['restaurant']['fail_book']['day'] and \
                            user_goal['restaurant']['book']['time'] == user_goal['restaurant']['fail_book']['time'] and \
                            user_goal['restaurant']['book']['people'] == user_goal['restaurant']['fail_book']['people']:
                        del user_goal['restaurant']['fail_book']

        # match day and people of hotel and train
        if 'hotel' in domain_ordering and 'train' in domain_ordering and \
                'book' in user_goal['hotel'] and 'info' in user_goal['train']:
            if user_goal['train']['info']['destination'] == 'cambridge' and \
                    'day' in user_goal['hotel']['book']:
                user_goal['train']['info']['day'] = user_goal['hotel']['book'][
                    'day']
            elif user_goal['train']['info']['departure'] == 'cambridge' and \
                    'day' in user_goal['hotel']['book'] and 'stay' in user_goal['hotel']['book']:
                user_goal['train']['info']['day'] = days[
                    (days.index(user_goal['hotel']['book']['day']) +
                     int(user_goal['hotel']['book']['stay'])) % 7]
            # In case, we have no query results with adjusted train goal, we simply drop the train goal.
            if len(
                    self.dbquery.query(
                        'train', user_goal['train']['info'].items())) == 0:
                del user_goal['train']
                domain_ordering.remove('train')

        user_goal['domain_ordering'] = domain_ordering

        return user_goal

    def _adjust_info(self, domain, info):
        # adjust one of the slots of the info
        # 随机选择一个slot进行随机替换
        adjusted_info = deepcopy(info)
        slot = random.choice(list(info.keys()))
        adjusted_info[slot] = random.choice(
            list(self.ind_slot_value_dist[domain]['info'][slot].keys()))
        return adjusted_info

    def build_message(self, user_goal, boldify=null_boldify):
        message = []
        state = deepcopy(user_goal)

        for dom in user_goal['domain_ordering']:
            state = deepcopy(user_goal[dom])

            if not (dom == 'taxi' and len(state['info']) == 1):
                # intro
                m = [templates[dom]['intro']]

            # info
            def fill_info_template(user_goal, domain, slot, info):
                if slot != 'area' or not (
                        'restaurant' in user_goal and 'attraction' in user_goal
                        and info in user_goal['restaurant'].keys()
                        and info in user_goal['attraction'].keys()
                        and 'area' in user_goal['restaurant'][info]
                        and 'area' in user_goal['attraction'][info]
                        and user_goal['restaurant'][info]['area']
                        == user_goal['attraction'][info]['area']):
                    return templates[domain][slot].format(
                        self.boldify(user_goal[domain][info][slot]))
                else:
                    restaurant_index = user_goal['domain_ordering'].index(
                        'restaurant')
                    attraction_index = user_goal['domain_ordering'].index(
                        'attraction')
                    if restaurant_index > attraction_index and domain == 'restaurant':
                        return templates[domain][slot].format(
                            self.boldify('same area as the attraction'))
                    elif attraction_index > restaurant_index and domain == 'attraction':
                        return templates[domain][slot].format(
                            self.boldify('same area as the restaurant'))
                return templates[domain][slot].format(
                    self.boldify(user_goal[domain][info][slot]))

            info = 'info'
            if 'fail_info' in user_goal[dom]:
                info = 'fail_info'
            if dom == 'taxi' and len(state[info]) == 1:
                taxi_index = user_goal['domain_ordering'].index('taxi')
                places = [
                    dom for dom in user_goal['domain_ordering'][:taxi_index]
                    if dom in ['attraction', 'hotel', 'restaurant']
                ]
                if len(places) >= 2:
                    random.shuffle(places)
                    m.append(templates['taxi']['commute'])
                    if 'arriveBy' in state[info]:
                        m.append(
                            'The taxi should arrive at the {} from the {} by {}.'
                            .format(self.boldify(places[0]),
                                    self.boldify(places[1]),
                                    self.boldify(state[info]['arriveBy'])))
                    elif 'leaveAt' in state[info]:
                        m.append(
                            'The taxi should leave from the {} to the {} after {}.'
                            .format(self.boldify(places[0]),
                                    self.boldify(places[1]),
                                    self.boldify(state[info]['leaveAt'])))
                    message.append(' '.join(m))
            else:
                while len(state[info]) > 0:
                    num_acts = random.randint(1, min(len(state[info]), 3))
                    slots = random.sample(list(state[info].keys()), num_acts)
                    sents = [
                        fill_info_template(user_goal, dom, slot, info)
                        for slot in slots
                        if slot not in ['parking', 'internet']
                    ]
                    if 'parking' in slots:
                        sents.append(templates[dom]['parking ' +
                                                    state[info]['parking']])
                    if 'internet' in slots:
                        sents.append(templates[dom]['internet ' +
                                                    state[info]['internet']])
                    m.extend(sents)
                    message.append(' '.join(m))
                    m = []
                    for slot in slots:
                        del state[info][slot]

            # fail_info
            if 'fail_info' in user_goal[dom]:
                adjusted_slot = list(
                    filter(
                        lambda x: x[0][1] != x[1][1],
                        zip(user_goal[dom]['info'].items(),
                            user_goal[dom]['fail_info'].items())))[0][0][0]
                if adjusted_slot in ['internet', 'parking']:
                    message.append(
                        templates[dom]['fail_info ' + adjusted_slot + ' ' +
                                       user_goal[dom]['info'][adjusted_slot]])
                else:
                    message.append(
                        templates[dom]['fail_info ' + adjusted_slot].format(
                            self.boldify(
                                user_goal[dom]['info'][adjusted_slot])))

            # reqt
            if 'reqt' in state:
                slot_strings = []
                for slot in state['reqt']:
                    if slot in ['internet', 'parking', 'food']:
                        continue
                    slot_strings.append(slot if slot not in
                                        request_slot_string_map else
                                        request_slot_string_map[slot])
                if len(slot_strings) > 0:
                    message.append(templates[dom]['request'].format(
                        self.boldify(', '.join(slot_strings))))
                if 'internet' in state['reqt']:
                    message.append(
                        'Make sure to ask if the hotel includes free wifi.')
                if 'parking' in state['reqt']:
                    message.append(
                        'Make sure to ask if the hotel includes free parking.')
                if 'food' in state['reqt']:
                    message.append(
                        'Make sure to ask about what food it serves.')

            def get_same_people_domain(user_goal, domain, slot):
                if slot not in ['day', 'people']:
                    return None
                domain_index = user_goal['domain_ordering'].index(domain)
                previous_domains = user_goal['domain_ordering'][:domain_index]
                for prev in previous_domains:
                    if prev in ['restaurant', 'hotel', 'train'] and 'book' in user_goal[prev] and \
                            slot in user_goal[prev]['book'] and user_goal[prev]['book'][slot] == \
                            user_goal[domain]['book'][slot]:
                        return prev
                return None

            # book
            book = 'book'
            if 'fail_book' in user_goal[dom]:
                book = 'fail_book'
            if 'book' in state:
                slot_strings = []
                for slot in ['people', 'time', 'day', 'stay']:
                    if slot in state[book]:
                        if slot == 'people':
                            same_people_domain = get_same_people_domain(
                                user_goal, dom, slot)
                            if same_people_domain is None:
                                slot_strings.append('for {} people'.format(
                                    self.boldify(state[book][slot])))
                            else:
                                slot_strings.append(
                                    self.boldify(
                                        'for the same group of people as the {} booking'
                                        .format(same_people_domain)))
                        elif slot == 'time':
                            slot_strings.append('at {}'.format(
                                self.boldify(state[book][slot])))
                        elif slot == 'day':
                            same_people_domain = get_same_people_domain(
                                user_goal, dom, slot)
                            if same_people_domain is None:
                                slot_strings.append('on {}'.format(
                                    self.boldify(state[book][slot])))
                            else:
                                slot_strings.append(
                                    self.boldify(
                                        'on the same day as the {} booking'.
                                        format(same_people_domain)))
                        elif slot == 'stay':
                            slot_strings.append('for {} nights'.format(
                                self.boldify(state[book][slot])))
                        del state[book][slot]

                assert len(state[book]) <= 0, state[book]

                if len(slot_strings) > 0:
                    message.append(templates[dom]['book'].format(
                        ' '.join(slot_strings)))

            # fail_book
            if 'fail_book' in user_goal[dom]:
                adjusted_slot = list(
                    filter(
                        lambda x: x[0][1] != x[1][1],
                        zip(user_goal[dom]['book'].items(),
                            user_goal[dom]['fail_book'].items())))[0][0][0]

                if adjusted_slot in ['internet', 'parking']:
                    message.append(
                        templates[dom]['fail_book ' + adjusted_slot + ' ' +
                                       user_goal[dom]['book'][adjusted_slot]])
                else:
                    message.append(
                        templates[dom]['fail_book ' + adjusted_slot].format(
                            self.boldify(
                                user_goal[dom]['book'][adjusted_slot])))

        if boldify == do_boldify:
            for i, m in enumerate(message):
                message[i] = message[i].replace('wifi', "<b>wifi</b>")
                message[i] = message[i].replace('internet', "<b>internet</b>")
                message[i] = message[i].replace('parking', "<b>parking</b>")

        return message
Esempio n. 19
0
class GoalGenerator:
    """User goal generator"""

    def __init__(self, data_dir,
                 goal_model_path,
                 corpus_path=None,
                 boldify=False):
        """
        Args:
            goal_model_path: path to a goal model 
            corpus_path: path to a dialog corpus to build a goal model 
        """
        self.dbquery = DBQuery(data_dir)
        self.goal_model_path = data_dir + '/' + goal_model_path
        self.corpus_path = data_dir + '/' + corpus_path if corpus_path is not None else None
        self.boldify = do_boldify if boldify else null_boldify
        if os.path.exists(self.goal_model_path):
            with open(self.goal_model_path, 'rb') as f:
                self.ind_slot_dist, self.ind_slot_value_dist, self.domain_ordering_dist, self.book_dist = pickle.load(f)
            logging.info('Loading goal model is done')
        else:
            self._build_goal_model()
            logging.info('Building goal model is done')

        # remove some slot
        del self.ind_slot_dist['police']['reqt']['postcode']
        del self.ind_slot_value_dist['police']['reqt']['postcode']
        del self.ind_slot_dist['hospital']['reqt']['postcode']
        del self.ind_slot_value_dist['hospital']['reqt']['postcode']
        del self.ind_slot_dist['hospital']['reqt']['address']
        del self.ind_slot_value_dist['hospital']['reqt']['address']

    def _build_goal_model(self):
        with open(self.corpus_path) as f:
            dialogs = json.load(f)

        # domain ordering
        def _get_dialog_domains(dialog):
            return list(filter(lambda x: x in domains and len(dialog['goal'][x]) > 0, dialog['goal']))

        domain_orderings = []
        for d in dialogs:
            d_domains = _get_dialog_domains(dialogs[d])
            first_index = []
            for domain in d_domains:
                message = [dialogs[d]['goal']['message']] if type(dialogs[d]['goal']['message']) == str else \
                dialogs[d]['goal']['message']
                for i, m in enumerate(message):
                    if domain_keywords[domain].lower() in m.lower() or domain.lower() in m.lower():
                        first_index.append(i)
                        break
            domain_orderings.append(tuple(map(lambda x: x[1], sorted(zip(first_index, d_domains), key=lambda x: x[0]))))
        domain_ordering_cnt = Counter(domain_orderings)
        self.domain_ordering_dist = deepcopy(domain_ordering_cnt)
        for order in domain_ordering_cnt.keys():
            self.domain_ordering_dist[order] = domain_ordering_cnt[order] / sum(domain_ordering_cnt.values())

        # independent goal slot distribution
        ind_slot_value_cnt = dict([(domain, {}) for domain in domains])
        domain_cnt = Counter()
        book_cnt = Counter()

        for d in dialogs:
            for domain in domains:
                if dialogs[d]['goal'][domain] != {}:
                    domain_cnt[domain] += 1
                if 'info' in dialogs[d]['goal'][domain]:
                    for slot in dialogs[d]['goal'][domain]['info']:
                        if 'invalid' in slot:
                            continue
                        if 'info' not in ind_slot_value_cnt[domain]:
                            ind_slot_value_cnt[domain]['info'] = {}
                        if slot not in ind_slot_value_cnt[domain]['info']:
                            ind_slot_value_cnt[domain]['info'][slot] = Counter()
                        if 'care' in dialogs[d]['goal'][domain]['info'][slot]:
                            continue
                        ind_slot_value_cnt[domain]['info'][slot][dialogs[d]['goal'][domain]['info'][slot]] += 1
                if 'reqt' in dialogs[d]['goal'][domain]:
                    for slot in dialogs[d]['goal'][domain]['reqt']:
                        if 'reqt' not in ind_slot_value_cnt[domain]:
                            ind_slot_value_cnt[domain]['reqt'] = Counter()
                        ind_slot_value_cnt[domain]['reqt'][slot] += 1
                if 'book' in dialogs[d]['goal'][domain]:
                    book_cnt[domain] += 1
                    for slot in dialogs[d]['goal'][domain]['book']:
                        if 'invalid' in slot:
                            continue
                        if 'book' not in ind_slot_value_cnt[domain]:
                            ind_slot_value_cnt[domain]['book'] = {}
                        if slot not in ind_slot_value_cnt[domain]['book']:
                            ind_slot_value_cnt[domain]['book'][slot] = Counter()
                        if 'care' in dialogs[d]['goal'][domain]['book'][slot]:
                            continue
                        ind_slot_value_cnt[domain]['book'][slot][dialogs[d]['goal'][domain]['book'][slot]] += 1

        self.ind_slot_value_dist = deepcopy(ind_slot_value_cnt)
        self.ind_slot_dist = dict([(domain, {}) for domain in domains])
        self.book_dist = {}
        for domain in domains:
            if 'info' in ind_slot_value_cnt[domain]:
                for slot in ind_slot_value_cnt[domain]['info']:
                    if 'info' not in self.ind_slot_dist[domain]:
                        self.ind_slot_dist[domain]['info'] = {}
                    if slot not in self.ind_slot_dist[domain]['info']:
                        self.ind_slot_dist[domain]['info'][slot] = {}
                    self.ind_slot_dist[domain]['info'][slot] = sum(ind_slot_value_cnt[domain]['info'][slot].values()) / \
                                                               domain_cnt[domain]
                    slot_total = sum(ind_slot_value_cnt[domain]['info'][slot].values())
                    for val in self.ind_slot_value_dist[domain]['info'][slot]:
                        self.ind_slot_value_dist[domain]['info'][slot][val] = ind_slot_value_cnt[domain]['info'][slot][
                                                                                  val] / slot_total
            if 'reqt' in ind_slot_value_cnt[domain]:
                for slot in ind_slot_value_cnt[domain]['reqt']:
                    if 'reqt' not in self.ind_slot_dist[domain]:
                        self.ind_slot_dist[domain]['reqt'] = {}
                    self.ind_slot_dist[domain]['reqt'][slot] = ind_slot_value_cnt[domain]['reqt'][slot] / domain_cnt[
                        domain]
                    self.ind_slot_value_dist[domain]['reqt'][slot] = ind_slot_value_cnt[domain]['reqt'][slot] / \
                                                                     domain_cnt[domain]
            if 'book' in ind_slot_value_cnt[domain]:
                for slot in ind_slot_value_cnt[domain]['book']:
                    if 'book' not in self.ind_slot_dist[domain]:
                        self.ind_slot_dist[domain]['book'] = {}
                    if slot not in self.ind_slot_dist[domain]['book']:
                        self.ind_slot_dist[domain]['book'][slot] = {}
                    self.ind_slot_dist[domain]['book'][slot] = sum(ind_slot_value_cnt[domain]['book'][slot].values()) / \
                                                               domain_cnt[domain]
                    slot_total = sum(ind_slot_value_cnt[domain]['book'][slot].values())
                    for val in self.ind_slot_value_dist[domain]['book'][slot]:
                        self.ind_slot_value_dist[domain]['book'][slot][val] = ind_slot_value_cnt[domain]['book'][slot][
                                                                                  val] / slot_total
            self.book_dist[domain] = book_cnt[domain] / len(dialogs)

        with open(self.goal_model_path, 'wb') as f:
            pickle.dump((self.ind_slot_dist, self.ind_slot_value_dist, self.domain_ordering_dist, self.book_dist), f)

    def _get_domain_goal(self, domain):
        cnt_slot = self.ind_slot_dist[domain]
        cnt_slot_value = self.ind_slot_value_dist[domain]
        pro_book = self.book_dist[domain]

        while True:
            # domain_goal = defaultdict(lambda: {})
            # domain_goal = {'info': {}, 'fail_info': {}, 'reqt': {}, 'book': {}, 'fail_book': {}}
            domain_goal = {'info': {}}
            # inform
            if 'info' in cnt_slot:
                for slot in cnt_slot['info']:
                    if random.random() < cnt_slot['info'][slot] + pro_correction['info']:
                        domain_goal['info'][slot] = nomial_sample(cnt_slot_value['info'][slot])

                if domain in ['hotel', 'restaurant', 'attraction'] and 'name' in domain_goal['info'] and len(
                        domain_goal['info']) > 1:
                    if random.random() < cnt_slot['info']['name']:
                        domain_goal['info'] = {'name': domain_goal['info']['name']}
                    else:
                        del domain_goal['info']['name']

                if domain in ['taxi', 'train'] and 'arriveBy' in domain_goal['info'] and 'leaveAt' in domain_goal[
                    'info']:
                    if random.random() < (
                            cnt_slot['info']['leaveAt'] / (cnt_slot['info']['arriveBy'] + cnt_slot['info']['leaveAt'])):
                        del domain_goal['info']['arriveBy']
                    else:
                        del domain_goal['info']['leaveAt']

                if domain in ['taxi', 'train'] and 'arriveBy' not in domain_goal['info'] and 'leaveAt' not in \
                        domain_goal['info']:
                    if random.random() < (cnt_slot['info']['arriveBy'] / (
                            cnt_slot['info']['arriveBy'] + cnt_slot['info']['leaveAt'])):
                        domain_goal['info']['arriveBy'] = nomial_sample(cnt_slot_value['info']['arriveBy'])
                    else:
                        domain_goal['info']['leaveAt'] = nomial_sample(cnt_slot_value['info']['leaveAt'])

                if domain in ['taxi', 'train'] and 'departure' not in domain_goal['info']:
                    domain_goal['info']['departure'] = nomial_sample(cnt_slot_value['info']['departure'])

                if domain in ['taxi', 'train'] and 'destination' not in domain_goal['info']:
                    domain_goal['info']['destination'] = nomial_sample(cnt_slot_value['info']['destination'])

                if domain in ['taxi', 'train'] and \
                        'departure' in domain_goal['info'] and \
                        'destination' in domain_goal['info'] and \
                        domain_goal['info']['departure'] == domain_goal['info']['destination']:
                    if random.random() < (cnt_slot['info']['departure'] / (
                            cnt_slot['info']['departure'] + cnt_slot['info']['destination'])):
                        domain_goal['info']['departure'] = nomial_sample(cnt_slot_value['info']['departure'])
                    else:
                        domain_goal['info']['destination'] = nomial_sample(cnt_slot_value['info']['destination'])
                if domain_goal['info'] == {}:
                    continue
            # request
            if 'reqt' in cnt_slot:
                reqt = [slot for slot in cnt_slot['reqt']
                        if random.random() < cnt_slot['reqt'][slot] + pro_correction['reqt'] and slot not in
                        domain_goal['info']]
                if len(reqt) > 0:
                    domain_goal['reqt'] = reqt

            # book
            if 'book' in cnt_slot and random.random() < pro_book + pro_correction['book']:
                if 'book' not in domain_goal:
                    domain_goal['book'] = {}

                for slot in cnt_slot['book']:
                    if random.random() < cnt_slot['book'][slot] + pro_correction['book']:
                        domain_goal['book'][slot] = nomial_sample(cnt_slot_value['book'][slot])

                # makes sure that there are all necessary slots for booking
                if domain == 'restaurant' and 'time' not in domain_goal['book']:
                    domain_goal['book']['time'] = nomial_sample(cnt_slot_value['book']['time'])

                if domain == 'hotel' and 'stay' not in domain_goal['book']:
                    domain_goal['book']['stay'] = nomial_sample(cnt_slot_value['book']['stay'])

                if domain in ['hotel', 'restaurant'] and 'day' not in domain_goal['book']:
                    domain_goal['book']['day'] = nomial_sample(cnt_slot_value['book']['day'])

                if domain in ['hotel', 'restaurant'] and 'people' not in domain_goal['book']:
                    domain_goal['book']['people'] = nomial_sample(cnt_slot_value['book']['people'])

                if domain == 'train' and len(domain_goal['book']) <= 0:
                    domain_goal['book']['people'] = nomial_sample(cnt_slot_value['book']['people'])

            # fail_book
            if 'book' in domain_goal and random.random() < 0.5:
                if domain == 'hotel':
                    domain_goal['fail_book'] = deepcopy(domain_goal['book'])
                    if 'stay' in domain_goal['book'] and random.random() < 0.5:
                        # increase hotel-stay
                        domain_goal['fail_book']['stay'] = str(int(domain_goal['book']['stay']) + 1)
                    elif 'day' in domain_goal['book']:
                        # push back hotel-day by a day
                        domain_goal['fail_book']['day'] = days[(days.index(domain_goal['book']['day']) - 1) % 7]

                elif domain == 'restaurant':
                    domain_goal['fail_book'] = deepcopy(domain_goal['book'])
                    if 'time' in domain_goal['book'] and random.random() < 0.5:
                        hour, minute = domain_goal['book']['time'].split(':')
                        domain_goal['fail_book']['time'] = str((int(hour) + 1) % 24) + ':' + minute
                    elif 'day' in domain_goal['book']:
                        if random.random() < 0.5:
                            domain_goal['fail_book']['day'] = days[(days.index(domain_goal['book']['day']) - 1) % 7]
                        else:
                            domain_goal['fail_book']['day'] = days[(days.index(domain_goal['book']['day']) + 1) % 7]

            # fail_info
            if 'info' in domain_goal and len(self.dbquery.query(domain, domain_goal['info'].items())) == 0:
                num_trial = 0
                while num_trial < 100:
                    adjusted_info = self._adjust_info(domain, domain_goal['info'])
                    if len(self.dbquery.query(domain, adjusted_info.items())) > 0:
                        if domain == 'train':
                            domain_goal['info'] = adjusted_info
                        else:
                            domain_goal['fail_info'] = domain_goal['info']
                            domain_goal['info'] = adjusted_info

                        break
                    num_trial += 1

                if num_trial >= 100:
                    continue

            # at least there is one request and book
            if 'reqt' in domain_goal or 'book' in domain_goal:
                break

        return domain_goal

    def get_user_goal(self, seed=None):
        # seed the generator to get fixed goal
        if seed is not None:
            random.seed(seed)
            np.random.seed(seed)
        
        domain_ordering = ()
        while len(domain_ordering) <= 0:
            domain_ordering = nomial_sample(self.domain_ordering_dist)
        #domain_ordering = ('attraction', 'restaurant', 'taxi')

        user_goal = {dom: self._get_domain_goal(dom) for dom in domain_ordering}
        assert len(user_goal.keys()) > 0

        # using taxi to communte between places, removing destination and departure.
        if 'taxi' in domain_ordering:
            places = [dom for dom in domain_ordering[: domain_ordering.index('taxi')] if 'address' in self.ind_slot_dist[dom]['reqt'].keys()]
            if len(places) >= 1:
                del user_goal['taxi']['info']['destination']
                user_goal[places[-1]]['reqt'] = list(set(user_goal[places[-1]].get('reqt', [])).union({'address'}))
                if places[-1] == 'restaurant' and 'book' in user_goal['restaurant']:
                    user_goal['taxi']['info']['arriveBy'] = user_goal['restaurant']['book']['time']
                    if 'leaveAt' in user_goal['taxi']['info']:
                        del user_goal['taxi']['info']['leaveAt']
            if len(places) >= 2:
                del user_goal['taxi']['info']['departure']
                user_goal[places[-2]]['reqt'] = list(set(user_goal[places[-2]].get('reqt', [])).union({'address'}))

        # match area of attraction and restaurant
        if 'restaurant' in domain_ordering and \
                'attraction' in domain_ordering and \
                'fail_info' not in user_goal['restaurant'] and \
                domain_ordering.index('restaurant') > domain_ordering.index('attraction') and \
                'area' in user_goal['restaurant']['info'] and 'area' in user_goal['attraction']['info']:
            adjusted_restaurant_goal = deepcopy(user_goal['restaurant']['info'])
            adjusted_restaurant_goal['area'] = user_goal['attraction']['info']['area']
            if len(self.dbquery.query('restaurant', adjusted_restaurant_goal.items())) > 0 and random.random() < 0.5:
                user_goal['restaurant']['info']['area'] = user_goal['attraction']['info']['area']

        # match day and people of restaurant and hotel
        if 'restaurant' in domain_ordering and 'hotel' in domain_ordering and \
                'book' in user_goal['restaurant'] and 'book' in user_goal['hotel']:
            if random.random() < 0.5:
                user_goal['restaurant']['book']['people'] = user_goal['hotel']['book']['people']
                if 'fail_book' in user_goal['restaurant']:
                    user_goal['restaurant']['fail_book']['people'] = user_goal['hotel']['book']['people']
            if random.random() < 1.0:
                user_goal['restaurant']['book']['day'] = user_goal['hotel']['book']['day']
                if 'fail_book' in user_goal['restaurant']:
                    user_goal['restaurant']['fail_book']['day'] = user_goal['hotel']['book']['day']
                    if user_goal['restaurant']['book']['day'] == user_goal['restaurant']['fail_book']['day'] and \
                            user_goal['restaurant']['book']['time'] == user_goal['restaurant']['fail_book']['time'] and \
                            user_goal['restaurant']['book']['people'] == user_goal['restaurant']['fail_book']['people']:
                        del user_goal['restaurant']['fail_book']

        # match day and people of hotel and train
        if 'hotel' in domain_ordering and 'train' in domain_ordering and \
                'book' in user_goal['hotel'] and 'info' in user_goal['train']:
            if user_goal['train']['info']['destination'] == 'cambridge' and \
                'day' in user_goal['hotel']['book']:
                user_goal['train']['info']['day'] = user_goal['hotel']['book']['day']
            elif user_goal['train']['info']['departure'] == 'cambridge' and \
                'day' in user_goal['hotel']['book'] and 'stay' in user_goal['hotel']['book']:
                user_goal['train']['info']['day'] = days[
                    (days.index(user_goal['hotel']['book']['day']) + int(
                        user_goal['hotel']['book']['stay'])) % 7]
            # In case, we have no query results with adjusted train goal, we simply drop the train goal.
            if len(self.dbquery.query('train', user_goal['train']['info'].items())) == 0:
                del user_goal['train']
                domain_ordering = tuple(list(domain_ordering).remove('train'))

        user_goal['domain_ordering'] = domain_ordering

        return user_goal

    def _adjust_info(self, domain, info):
        # adjust one of the slots of the info
        adjusted_info = deepcopy(info)
        slot = random.choice(list(info.keys()))
        adjusted_info[slot] = random.choice(list(self.ind_slot_value_dist[domain]['info'][slot].keys()))
        return adjusted_info

    def build_message(self, user_goal, boldify=null_boldify):
        message = []
        state = deepcopy(user_goal)

        for dom in user_goal['domain_ordering']:
            state = deepcopy(user_goal[dom])

            if not (dom == 'taxi' and len(state['info']) == 1):
                # intro
                m = [templates[dom]['intro']]

            # info
            def fill_info_template(user_goal, domain, slot, info):
                if slot != 'area' or not ('restaurant' in user_goal and
                                          'attraction' in user_goal and
                                          info in user_goal['restaurant'].keys() and
                                          info in user_goal['attraction'].keys() and
                                          'area' in user_goal['restaurant'][info] and
                                          'area' in user_goal['attraction'][info] and
                                          user_goal['restaurant'][info]['area'] == user_goal['attraction'][info]['area']):
                    return templates[domain][slot].format(self.boldify(user_goal[domain][info][slot]))
                else:
                    restaurant_index = user_goal['domain_ordering'].index('restaurant')
                    attraction_index = user_goal['domain_ordering'].index('attraction')
                    if restaurant_index > attraction_index and domain == 'restaurant':
                        return templates[domain][slot].format(self.boldify('same area as the attraction'))
                    elif attraction_index > restaurant_index and domain == 'attraction':
                        return templates[domain][slot].format(self.boldify('same area as the restaurant'))
                return templates[domain][slot].format(self.boldify(user_goal[domain][info][slot]))

            info = 'info'
            if 'fail_info' in user_goal[dom]:
                info = 'fail_info'
            if dom == 'taxi' and len(state[info]) == 1:
                taxi_index = user_goal['domain_ordering'].index('taxi')
                places = [dom for dom in user_goal['domain_ordering'][: taxi_index] if
                          dom in ['attraction', 'hotel', 'restaurant']]
                if len(places) >= 2:
                    random.shuffle(places)
                    m.append(templates['taxi']['commute'])
                    if 'arriveBy' in state[info]:
                        m.append('The taxi should arrive at the {} from the {} by {}.'.format(self.boldify(places[0]),
                                                                                              self.boldify(places[1]),
                                                                                              self.boldify(state[info]['arriveBy'])))
                    elif 'leaveAt' in state[info]:
                        m.append('The taxi should leave from the {} to the {} after {}.'.format(self.boldify(places[0]),
                                                                                                self.boldify(places[1]),
                                                                                                self.boldify(state[info]['leaveAt'])))
                    message.append(' '.join(m))
            else:
                while len(state[info]) > 0:
                    num_acts = random.randint(1, min(len(state[info]), 3))
                    slots = random.sample(list(state[info].keys()), num_acts)
                    sents = [fill_info_template(user_goal, dom, slot, info) for slot in slots if slot not in ['parking', 'internet']]
                    if 'parking' in slots:
                        sents.append(templates[dom]['parking ' + state[info]['parking']])
                    if 'internet' in slots:
                        sents.append(templates[dom]['internet ' + state[info]['internet']])
                    m.extend(sents)
                    message.append(' '.join(m))
                    m = []
                    for slot in slots:
                        del state[info][slot]

            # fail_info
            if 'fail_info' in user_goal[dom]:
            # if 'fail_info' in user_goal[dom]:
                adjusted_slot = list(filter(lambda x: x[0][1] != x[1][1],
                                            zip(user_goal[dom]['info'].items(), user_goal[dom]['fail_info'].items())))[0][0][0]
                if adjusted_slot in ['internet', 'parking']:
                    message.append(templates[dom]['fail_info ' + adjusted_slot + ' ' + user_goal[dom]['info'][adjusted_slot]])
                else:
                    message.append(templates[dom]['fail_info ' + adjusted_slot].format(self.boldify(user_goal[dom]['info'][adjusted_slot])))

            # reqt
            if 'reqt' in state:
                slot_strings = []
                for slot in state['reqt']:
                    if slot in ['internet', 'parking', 'food']:
                        continue
                    slot_strings.append(slot if slot not in request_slot_string_map else request_slot_string_map[slot])
                if len(slot_strings) > 0:
                    message.append(templates[dom]['request'].format(self.boldify(', '.join(slot_strings))))
                if 'internet' in state['reqt']:
                    message.append('Make sure to ask if the hotel includes free wifi.')
                if 'parking' in state['reqt']:
                    message.append('Make sure to ask if the hotel includes free parking.')
                if 'food' in state['reqt']:
                    message.append('Make sure to ask about what food it serves.')

            def get_same_people_domain(user_goal, domain, slot):
                if slot not in ['day', 'people']:
                    return None
                domain_index = user_goal['domain_ordering'].index(domain)
                previous_domains = user_goal['domain_ordering'][:domain_index]
                for prev in previous_domains:
                    if prev in ['restaurant', 'hotel', 'train'] and 'book' in user_goal[prev] and \
                            slot in user_goal[prev]['book'] and user_goal[prev]['book'][slot] == \
                            user_goal[domain]['book'][slot]:
                        return prev
                return None

            # book
            book = 'book'
            if 'fail_book' in user_goal[dom]:
                book = 'fail_book'
            if 'book' in state:
                slot_strings = []
                for slot in ['people', 'time', 'day', 'stay']:
                    if slot in state[book]:
                        if slot == 'people':
                            same_people_domain = get_same_people_domain(user_goal, dom, slot)
                            if same_people_domain is None:
                                slot_strings.append('for {} people'.format(self.boldify(state[book][slot])))
                            else:
                                slot_strings.append(self.boldify(
                                    'for the same group of people as the {} booking'.format(same_people_domain)))
                        elif slot == 'time':
                            slot_strings.append('at {}'.format(self.boldify(state[book][slot])))
                        elif slot == 'day':
                            same_people_domain = get_same_people_domain(user_goal, dom, slot)
                            if same_people_domain is None:
                                slot_strings.append('on {}'.format(self.boldify(state[book][slot])))
                            else:
                                slot_strings.append(
                                    self.boldify('on the same day as the {} booking'.format(same_people_domain)))
                        elif slot == 'stay':
                            slot_strings.append('for {} nights'.format(self.boldify(state[book][slot])))
                        del state[book][slot]

                assert len(state[book]) <= 0, state[book]

                if len(slot_strings) > 0:
                    message.append(templates[dom]['book'].format(' '.join(slot_strings)))

            # fail_book
            if 'fail_book' in user_goal[dom]:
                adjusted_slot = list(filter(lambda x: x[0][1] != x[1][1], zip(user_goal[dom]['book'].items(),
                                                                              user_goal[dom]['fail_book'].items())))[0][0][0]

                if adjusted_slot in ['internet', 'parking']:
                    message.append(
                        templates[dom]['fail_book ' + adjusted_slot + ' ' + user_goal[dom]['book'][adjusted_slot]])
                else:
                    message.append(templates[dom]['fail_book ' + adjusted_slot].format(
                        self.boldify(user_goal[dom]['book'][adjusted_slot])))

        if boldify == do_boldify:
            for i, m in enumerate(message):
                message[i] = message[i].replace('wifi', "<b>wifi</b>")
                message[i] = message[i].replace('internet', "<b>internet</b>")
                message[i] = message[i].replace('parking', "<b>parking</b>")

        return message
Esempio n. 20
0
class StateTracker(object):
    def __init__(self, data_dir, config):
        self.time_step = 0
        self.cfg = config
        self.db = DBQuery(data_dir, config)
        self.topic = ''
        self.evaluator = MultiWozEvaluator(data_dir)
        self.lock_evalutor = False

    def set_rollout(self, rollout):
        if rollout:
            self.save_time_step = self.time_step
            self.save_topic = self.topic
            self.lock_evalutor = True
        else:
            self.time_step = self.save_time_step
            self.save_topic = self.topic
            self.lock_evalutor = False

    def get_entities(self, s, domain):
        origin = s['belief_state'][domain].items()
        constraint = []
        for k, v in origin:
            if v != '?' and k in self.cfg.mapping[domain]:
                constraint.append((self.cfg.mapping[domain][k], v))
        entities = self.db.query(domain, constraint)
        random.shuffle(entities)
        return entities

    def update_belief_sys(self, old_s, a):
        """
        update belief/goal state with sys action
        """
        s = deepcopy(old_s)
        a_index = torch.nonzero(a)  # get multiple da indices

        self.time_step += 1
        s['others']['turn'] = self.time_step

        # update sys/user dialog act
        s['sys_action'] = dict()

        # update belief part
        das = [self.cfg.idx2da[idx.item()] for idx in a_index]
        das = [da.split('-') for da in das]
        sorted(das, key=lambda x: x[0])  # sort by domain

        entities = [] if self.topic == '' else self.get_entities(s, self.topic)
        return_flag = False
        for domain, intent, slot, p in das:
            if domain in self.cfg.belief_domains and domain != self.topic:
                self.topic = domain
                entities = self.get_entities(s, domain)

            da = '-'.join((domain, intent, slot, p))
            if intent == 'request':
                s['sys_action'][da] = '?'
            elif intent in ['nooffer', 'nobook'] and self.topic != '':
                return_flag = True
                if slot in s['belief_state'][self.topic] and s['belief_state'][
                        self.topic][slot] != '?':
                    s['sys_action'][da] = s['belief_state'][self.topic][slot]
                else:
                    s['sys_action'][da] = 'none'
            elif slot == 'choice':
                s['sys_action'][da] = str(len(entities))
            elif slot == 'none':
                s['sys_action'][da] = 'none'
            else:
                num = int(p) - 1
                if self.topic and len(
                        entities) > num and slot in self.cfg.mapping[
                            self.topic]:
                    typ = self.cfg.mapping[self.topic][slot]
                    if typ in entities[num]:
                        s['sys_action'][da] = entities[num][typ]
                    else:
                        s['sys_action'][da] = 'none'
                else:
                    s['sys_action'][da] = 'none'

                if not self.topic:
                    continue
                if intent in [
                        'inform', 'recommend', 'offerbook', 'offerbooked',
                        'book'
                ]:
                    discard(s['belief_state'][self.topic], slot, '?')
                    if slot in s['user_goal'][self.topic] and s['user_goal'][
                            self.topic][slot] == '?':
                        s['goal_state'][self.topic][slot] = s['sys_action'][da]

                # booked
                if intent == 'inform' and slot == 'car':  # taxi
                    if 'booked' not in s['belief_state']['taxi']:
                        s['belief_state']['taxi']['booked'] = 'taxi-booked'
                elif intent in ['offerbooked', 'book'
                                ] and slot == 'ref':  # train
                    if self.topic in ['taxi', 'hospital', 'police']:
                        s['belief_state'][
                            self.topic]['booked'] = f'{self.topic}-booked'
                        s['sys_action'][da] = f'{self.topic}-booked'
                    elif entities:
                        book_domain = entities[0]['ref'].split('-')[0]
                        if 'booked' not in s['belief_state'][
                                book_domain] and entities:
                            s['belief_state'][book_domain][
                                'booked'] = entities[0]['ref']
                            s['sys_action'][da] = entities[0]['ref']

        if return_flag:
            for da in s['user_action']:
                d_usr, i_usr, s_usr = da.split('-')
                if i_usr == 'inform' and d_usr == self.topic:
                    discard(s['belief_state'][d_usr], s_usr)
            reload(s['goal_state'], s['user_goal'], self.topic)

        if not self.lock_evalutor:
            self.evaluator.add_sys_da(s['sys_action'])

        return s

    def update_belief_usr(self, old_s, a):
        """
        update belief/goal state with user action
        """
        s = deepcopy(old_s)
        a_index = torch.nonzero(a)  # get multiple da indices

        self.time_step += 1
        s['others']['turn'] = self.time_step
        s['others']['terminal'] = 1 if (self.cfg.a_dim_usr -
                                        1) in a_index else 0

        # update sys/user dialog act
        s['user_action'] = dict()

        # update belief part
        das = [
            self.cfg.idx2da_u[idx.item()] for idx in a_index
            if idx.item() != self.cfg.a_dim_usr - 1
        ]
        das = [da.split('-') for da in das]
        if s['invisible_domains']:
            for da in das:
                if da[0] == s['next_available_domain']:
                    s['next_available_domain'] = s['invisible_domains'][0]
                    s['invisible_domains'].remove(s['next_available_domain'])
                    break
        sorted(das, key=lambda x: x[0])  # sort by domain

        for domain, intent, slot in das:
            if domain in self.cfg.belief_domains and domain != self.topic:
                self.topic = domain

            da = '-'.join((domain, intent, slot))
            if intent == 'request':
                s['user_action'][da] = '?'
                s['belief_state'][self.topic][slot] = '?'
            elif slot == 'none':
                s['user_action'][da] = 'none'
            else:
                if self.topic and slot in s['user_goal'][
                        self.topic] and s['user_goal'][domain][slot] != '?':
                    s['user_action'][da] = s['user_goal'][domain][slot]
                else:
                    s['user_action'][da] = 'dont care'

                if not self.topic:
                    continue
                if intent == 'inform':
                    s['belief_state'][domain][slot] = s['user_action'][da]
                    if slot in s['user_goal'][self.topic] and s['user_goal'][
                            self.topic][slot] != '?':
                        discard(s['goal_state'][self.topic], slot)

        if not self.lock_evalutor:
            self.evaluator.add_usr_da(s['user_action'])

        return s

    def reset(self, random_seed=None):
        """
        Args:
            random_seed (int):
        Returns:
            init_state (dict):
        """
        pass

    def step(self, s, sys_a):
        """
        Args:
            s (dict):
            sys_a (vector):
        Returns:
            next_s (dict):
            terminal (bool):
        """
        pass
Esempio n. 21
0
    def __init__(self,
                 env_cls,
                 args,
                 manager,
                 cfg,
                 process_num,
                 pre=False,
                 pre_irl=False,
                 infer=False,
                 realtrain=False):
        """
        :param env_cls: env class or function, not instance, as we need to create several instance in class.
        :param args:
        :param manager:
        :param cfg:
        :param process_num: process number
        :param pre: set to pretrain mode
        :param infer: set to test mode
        """
        self.cfg = cfg
        self.policy_clip = args.policy_clip
        self.train_direc = args.train_direc
        self.realtrain = realtrain
        self.process_num = process_num
        self.human_reward = human_reward(args, cfg)
        self.gan_type = args.gan_type
        # initialize envs for each process
        self.env_list = []
        for _ in range(process_num):
            self.env_list.append(env_cls())

        self.policy = MultiDiscretePolicy(cfg).to(device=DEVICE)
        self.value = Value(cfg).to(device=DEVICE)
        logging.info(summary(self.policy, show_weights=False))
        # logging.info(summary(self.value , show_weights=False))

        self.multi_entropy_loss = nn.MultiLabelSoftMarginLoss()
        self.loss = nn.BCELoss()

        # if pre:
        self.print_per_batch = args.print_per_batch
        from dbquery import DBQuery
        db = DBQuery(args.data_dir)
        self.data_train = manager.create_dataset_seq('train', args.batchsz,
                                                     cfg, db)
        self.data_valid = manager.create_dataset_seq('valid', args.batchsz,
                                                     cfg, db)
        self.data_test = manager.create_dataset_seq('test', args.batchsz, cfg,
                                                    db)
        self.multi_entropy_loss = nn.MultiLabelSoftMarginLoss()
        self.evaluator = Evaluator(args.data_dir, cfg)

        # else:
        #     # self.rewarder = RewardEstimator(args, manager, cfg, pretrain=pre_irl, inference=infer, feature_extractor=self.policy)
        #     self.rewarder = DiscEstimator(args, manager, cfg, pretrain=pre_irl, inference=infer)
        #     self.evaluator = Evaluator(args.data_dir, cfg)
        #     from dbquery import DBQuery
        #     db = DBQuery(args.data_dir)
        #     self.data_train = manager.create_dataset_seq('train', args.batchsz, cfg, db)
        self.expert_iter = iter(self.data_train)

        self.save_dir = args.save_dir
        self.save_per_epoch = args.save_per_epoch
        self.optim_batchsz = args.batchsz
        self.update_round = args.update_round
        self.policy.eval()

        self.gamma = args.gamma
        self.epsilon = args.epsilon
        self.tau = args.tau
        self.policy_optim = optim.Adam(self.policy.parameters(), lr=args.lr_rl)
        self.value_optim = optim.Adam(self.value.parameters(), lr=args.lr_rl)
        self.mt_factor = args.mt_factor
        self.epoch = 0
        self.best_valid_loss = np.inf
        self.valid_loss_threshold = np.inf
        self.patience = 10
Esempio n. 22
0
 def __init__(self, data_dir, config):
     self.time_step = 0
     self.cfg = config
     self.db = DBQuery(data_dir)
     self.topic = 'NONE'