class IrBaselineAgent(Agent): """Information Retrieval baseline.""" @staticmethod def add_cmdline_args(parser): """Add command line args specific to this agent.""" parser = parser.add_argument_group('IrBaseline Arguments') parser.add_argument('-lp', '--length_penalty', type=float, default=0.5, help='length penalty for responses') parser.add_argument( '-hsz', '--history_size', type=int, default=1, help='number of utterances from the dialogue history to take use ' 'as the query') parser.add_argument('--label_candidates_file', type=str, default=None, help='file of candidate responses to choose from') def __init__(self, opt, shared=None): """Initialize agent.""" super().__init__(opt) self.id = 'IRBaselineAgent' self.length_penalty = float(opt['length_penalty']) self.dictionary = DictionaryAgent(opt) self.opt = opt self.history = [] self.episodeDone = True if opt.get('label_candidates_file'): f = open(opt.get('label_candidates_file')) self.label_candidates = f.read().split('\n') def reset(self): """Reset agent properties.""" self.observation = None self.history = [] self.episodeDone = True def observe(self, obs): """Store and remember incoming observation message dict.""" self.observation = obs self.dictionary.observe(obs) if self.episodeDone: self.history = [] if 'text' in obs: self.history.append(obs.get('text', '')) self.episodeDone = obs.get('episode_done', False) return obs def act(self): """Generate a response to the previously seen observation(s).""" if self.opt.get('datatype', '').startswith('train'): self.dictionary.act() obs = self.observation reply = {} reply['id'] = self.getID() # Rank candidates cands = None if 'label_candidates' in obs and len(obs['label_candidates']) > 0: cands = obs['label_candidates'] if hasattr(self, 'label_candidates'): # override label candidates with candidate file if set cands = self.label_candidates if cands: hist_sz = self.opt.get('history_size', 1) left_idx = max(0, len(self.history) - hist_sz) text = ' '.join(self.history[left_idx:len(self.history)]) rep = self.build_query_representation(text) reply['text_candidates'] = (rank_candidates( rep, cands, self.length_penalty, self.dictionary)) reply['text'] = reply['text_candidates'][0] else: reply['text'] = "I don't know." return reply def save(self, fname=None): """Save dictionary tokenizer if available.""" fname = self.opt.get('model_file', None) if fname is None else fname if fname: self.dictionary.save(fname + '.dict') def load(self, fname): """Load internal dictionary.""" self.dictionary.load(fname + '.dict') def build_query_representation(self, query): """Build representation of query, e.g. words or n-grams. :param query: string to represent. :returns: dictionary containing 'words' dictionary (token => frequency) and 'norm' float (square root of the number of tokens) """ rep = {} rep['words'] = {} words = [w for w in self.dictionary.tokenize(query.lower())] rw = rep['words'] used = {} for w in words: if len(self.dictionary.freqs()) > 0: rw[w] = 1.0 / (1.0 + math.log(1.0 + self.dictionary.freqs()[w])) else: if w not in stopwords: rw[w] = 1 used[w] = True rep['norm'] = math.sqrt(len(words)) return rep
class IrBaselineAgent(Agent): @staticmethod def add_cmdline_args(parser): DictionaryAgent.add_cmdline_args(parser) parser.add_argument( '-lp', '--length_penalty', type=float, default=0.5, help='length penalty for responses') parser.add_argument( '-hsz', '--history_size', type=int, default=1, help='number of utterances from the dialogue history to take use as the query') def __init__(self, opt, shared=None): super().__init__(opt) self.id = 'IRBaselineAgent' self.length_penalty = float(opt['length_penalty']) self.dictionary = DictionaryAgent(opt) self.opt = opt self.history = [] self.episodeDone = True def reset(self): self.observation = None self.history = [] self.episodeDone = True def observe(self, obs): self.observation = obs self.dictionary.observe(obs) if self.episodeDone: self.history = [] if 'text' in obs: self.history.append(obs.get('text', '')) self.episodeDone = obs.get('episode_done', False) return obs def act(self): if self.opt.get('datatype', '').startswith('train'): self.dictionary.act() obs = self.observation reply = {} reply['id'] = self.getID() # Rank candidates if 'label_candidates' in obs and len(obs['label_candidates']) > 0: # text = obs['text'] text = ' '.join( self.history[max(0, len(self.history) - self.opt.get('history_size', 1)):len(self.history)]) rep = self.build_query_representation(text) reply['text_candidates'] = ( rank_candidates(rep, obs['label_candidates'], self.length_penalty, self.dictionary)) reply['text'] = reply['text_candidates'][0] else: reply['text'] = "I don't know." return reply def save(self, fname=None): fname = self.opt.get('model_file', None) if fname is None else fname if fname: self.dictionary.save(fname + '.dict') def load(self, fname): self.dictionary.load(fname + '.dict') def build_query_representation(self, query): """ Build representation of query, e.g. words or n-grams """ rep = {} rep['words'] = {} words = [w for w in self.dictionary.tokenize(query.lower())] rw = rep['words'] used = {} for w in words: if len(self.dictionary.freqs()) > 0: rw[w] = 1.0 / (1.0 + math.log(1.0 + self.dictionary.freqs()[w])) else: if w not in stopwords: rw[w] = 1 used[w] = True rep['norm'] = math.sqrt(len(words)) return rep
class MemnnAgent(Agent): """ Memory Network agent. """ @staticmethod def add_cmdline_args(argparser): DictionaryAgent.add_cmdline_args(argparser) argparser.add_arg('-lr', '--learning-rate', type=float, default=0.01, help='learning rate') argparser.add_arg('--embedding-size', type=int, default=128, help='size of token embeddings') argparser.add_arg('--hops', type=int, default=3, help='number of memory hops') argparser.add_arg('--mem-size', type=int, default=100, help='size of memory') argparser.add_arg('--time-features', type='bool', default=True, help='use time features for memory embeddings') argparser.add_arg('--position-encoding', type='bool', default=False, help='use position encoding instead of bag of words embedding') argparser.add_arg('--optimizer', default='adam', help='optimizer type (sgd|adam)') argparser.add_argument('--no-cuda', action='store_true', default=False, help='disable GPUs even if available') argparser.add_arg('--gpu', type=int, default=-1, help='which GPU device to use') def __init__(self, opt, shared=None): opt['cuda'] = not opt['no_cuda'] and torch.cuda.is_available() if opt['cuda']: print('[ Using CUDA ]') torch.cuda.device(opt['gpu']) if not shared: self.opt = opt self.id = 'MemNN' self.dict = DictionaryAgent(opt) freqs = torch.LongTensor(list(self.dict.freqs().values())) self.model = MemNN(opt, freqs) self.mem_size = opt['mem_size'] self.loss_fn = CrossEntropyLoss() self.answers = [None] * opt['batchsize'] optim_params = [p for p in self.model.parameters() if p.requires_grad] if opt['optimizer'] == 'sgd': self.optimizer = optim.SGD(optim_params, lr=opt['learning_rate']) elif opt['optimizer'] == 'adam': self.optimizer = optim.Adam(optim_params, lr=opt['learning_rate']) else: raise NotImplementedError('Optimizer not supported.') if opt['cuda']: self.model.share_memory() if opt.get('model_file') and os.path.isfile(opt['model_file']): print('Loading existing model parameters from ' + opt['model_file']) self.load(opt['model_file']) else: self.answers = shared['answers'] self.episode_done = True self.last_cands, self.last_cands_list = None, None super().__init__(opt, shared) def share(self): shared = super().share() shared['answers'] = self.answers return shared def observe(self, observation): observation = copy.copy(observation) if not self.episode_done: # if the last example wasn't the end of an episode, then we need to # recall what was said in that example prev_dialogue = self.observation['text'] batch_idx = self.opt.get('batchindex', 0) if self.answers[batch_idx] is not None: prev_dialogue += '\n' + self.answers[batch_idx] self.answers[batch_idx] = None observation['text'] = prev_dialogue + '\n' + observation['text'] self.observation = observation self.episode_done = observation['episode_done'] return observation def update(self, xs, ys, cands): self.model.train() self.optimizer.zero_grad() # Organize inputs for network (see contents of xs and ys in batchify method) inputs = [xs[0], xs[1], ys[0], xs[2], xs[3], ys[1]] inputs = [Variable(x) for x in inputs] output_embeddings, answer_embeddings = self.model(*inputs) scores = self.score(cands, output_embeddings, answer_embeddings) label_inds = [cand_list.index(self.labels[i]) for i, cand_list in enumerate(cands)] label_inds = Variable(torch.LongTensor(label_inds)) if self.opt['cuda']: label_inds = label_inds.cuda(async=True) loss = self.loss_fn(scores, label_inds) loss.backward() self.optimizer.step() return self.ranked_predictions(cands, scores) def predict(self, xs, cands): self.model.eval() # Organize inputs for network (see contents of xs in batchify method) inputs = [xs[0], xs[1], None, xs[2], xs[3], None] inputs = [Variable(x, volatile=True) for x in inputs] output_embeddings, _ = self.model(*inputs) scores = self.score(cands, output_embeddings) return self.ranked_predictions(cands, scores) def score(self, cands, output_embeddings, answer_embeddings=None): last_cand = None max_len = max([len(c) for c in cands]) scores = Variable(torch.Tensor(len(cands), max_len).fill_(-float('inf'))) if self.opt['cuda']: scores = scores.cuda(async=True) for i, cand_list in enumerate(cands): if last_cand != cand_list: candidate_lengths, candidate_indices = to_tensors(cand_list, self.dict) candidate_lengths, candidate_indices = Variable(candidate_lengths), Variable(candidate_indices) candidate_embeddings = self.model.answer_embedder(candidate_lengths, candidate_indices) if self.opt['cuda']: candidate_embeddings = candidate_embeddings.cuda(async=True) last_cand = cand_list scores[i, :len(cand_list)] = self.model.score.one_to_many(output_embeddings[i].unsqueeze(0), candidate_embeddings) return scores def ranked_predictions(self, cands, scores): _, inds = scores.data.sort(descending=True, dim=1) return [[cands[i][j] for j in r if j < len(cands[i])] for i, r in enumerate(inds)] def parse(self, text): """Returns: query = tensor (vector) of token indices for query query_length = length of query memory = tensor (matrix) where each row contains token indices for a memory memory_lengths = tensor (vector) with lengths of each memory """ sp = text.split('\n') query_sentence = sp[-1] query = self.dict.txt2vec(query_sentence) query = torch.LongTensor(query) query_length = torch.LongTensor([len(query)]) sp = sp[:-1] sentences = [] for s in sp: sentences.extend(s.split('\t')) if len(sentences) == 0: sentences.append(self.dict.null_token) num_mems = min(self.mem_size, len(sentences)) memory_sentences = sentences[-num_mems:] memory = [self.dict.txt2vec(s) for s in memory_sentences] memory = [torch.LongTensor(m) for m in memory] memory_lengths = torch.LongTensor([len(m) for m in memory]) memory = torch.cat(memory) return (query, memory, query_length, memory_lengths) def batchify(self, obs): """Returns: xs = [memories, queries, memory_lengths, query_lengths] ys = [labels, label_lengths] (if available, else None) cands = list of candidates for each example in batch valid_inds = list of indices for examples with valid observations """ exs = [ex for ex in obs if 'text' in ex] valid_inds = [i for i, ex in enumerate(obs) if 'text' in ex] parsed = [self.parse(ex['text']) for ex in exs] queries = torch.cat([x[0] for x in parsed]) memories = torch.cat([x[1] for x in parsed]) query_lengths = torch.cat([x[2] for x in parsed]) memory_lengths = torch.LongTensor(len(exs), self.mem_size).zero_() for i in range(len(exs)): if len(parsed[i][3]) > 0: memory_lengths[i, -len(parsed[i][3]):] = parsed[i][3] xs = [memories, queries, memory_lengths, query_lengths] ys = None self.labels = [random.choice(ex['labels']) for ex in exs if 'labels' in ex] if len(self.labels) == len(exs): parsed = [self.dict.txt2vec(l) for l in self.labels] parsed = [torch.LongTensor(p) for p in parsed] label_lengths = torch.LongTensor([len(p) for p in parsed]).unsqueeze(1) labels = torch.cat(parsed) ys = [labels, label_lengths] cands = [ex['label_candidates'] for ex in exs if 'label_candidates' in ex] # Use words in dict as candidates if no candidates are provided if len(cands) < len(exs): cands = build_cands(exs, self.dict) # Avoid rebuilding candidate list every batch if its the same if self.last_cands != cands: self.last_cands = cands self.last_cands_list = [list(c) for c in cands] cands = self.last_cands_list return xs, ys, cands, valid_inds def batch_act(self, observations): batchsize = len(observations) batch_reply = [{'id': self.getID()} for _ in range(batchsize)] xs, ys, cands, valid_inds = self.batchify(observations) if len(xs[1]) == 0: return batch_reply # Either train or predict if ys is not None: predictions = self.update(xs, ys, cands) else: predictions = self.predict(xs, cands) for i in range(len(valid_inds)): self.answers[valid_inds[i]] = predictions[i][0] batch_reply[valid_inds[i]]['text'] = predictions[i][0] batch_reply[valid_inds[i]]['text_candidates'] = predictions[i] return batch_reply def act(self): return self.batch_act([self.observation])[0] def save(self, path=None): path = self.opt.get('model_file', None) if path is None else path if path: model_state = self.model.state_dict() optim_state = self.optimizer.state_dict() with open(path, 'wb') as write: torch.save((model_state, optim_state), write) def load(self, path): with open(path, 'rb') as read: (model, optim) = torch.load(read) self.model.load_state_dict(model) self.optimizer.load_state_dict(optim)
class IrBaselineAgent(Agent): @staticmethod def add_cmdline_args(parser): DictionaryAgent.add_cmdline_args(parser) parser.add_argument( '-lp', '--length_penalty', default=0.5, help='length penalty for responses') def __init__(self, opt, shared=None): super().__init__(opt) self.id = 'IRBaselineAgent' self.length_penalty = float(opt['length_penalty']) self.dictionary = DictionaryAgent(opt) self.opt = opt def observe(self, obs): self.observation = obs self.dictionary.observe(obs) return obs def act(self): if self.opt.get('datatype', '').startswith('train'): self.dictionary.act() obs = self.observation reply = {} reply['id'] = self.getID() # Rank candidates if 'label_candidates' in obs and len(obs['label_candidates']) > 0: rep = self.build_query_representation(obs['text']) reply['text_candidates'] = ( rank_candidates(rep, obs['label_candidates'], self.length_penalty, self.dictionary)) reply['text'] = reply['text_candidates'][0] else: reply['text'] = "I don't know." return reply def save(self, fname=None): fname = self.opt.get('model_file', None) if fname is None else fname if fname: self.dictionary.save(fname + '.dict') def load(self, fname): self.dictionary.load(fname + '.dict') def build_query_representation(self, query): """ Build representation of query, e.g. words or n-grams """ rep = {} rep['words'] = {} words = [w for w in self.dictionary.tokenize(query.lower())] rw = rep['words'] used = {} for w in words: if len(self.dictionary.freqs()) > 0: rw[w] = 1.0 / (1.0 + math.log(1.0 + self.dictionary.freqs()[w])) else: if w not in stopwords: rw[w] = 1 used[w] = True norm = len(used) rep['norm'] = math.sqrt(len(words)) return rep
class IrBaselineAgent(Agent): @staticmethod def add_cmdline_args(parser): DictionaryAgent.add_cmdline_args(parser) parser.add_argument('-lp', '--length_penalty', default=0.5, help='length penalty for responses') def __init__(self, opt, shared=None): super().__init__(opt) self.id = 'IRBaselineAgent' self.length_penalty = float(opt['length_penalty']) self.dictionary = DictionaryAgent(opt) self.opt = opt def observe(self, obs): self.observation = obs self.dictionary.observe(obs) return obs def act(self): if self.opt.get('datatype', '').startswith('train'): self.dictionary.act() obs = self.observation reply = {} reply['id'] = self.getID() # Rank candidates if 'label_candidates' in obs and len(obs['label_candidates']) > 0: rep = self.build_query_representation(obs['text']) reply['text_candidates'] = (rank_candidates( rep, obs['label_candidates'], self.length_penalty, self.dictionary)) reply['text'] = reply['text_candidates'][0] else: reply['text'] = "I don't know." return reply def save(self, fname=None): fname = self.opt.get('model_file', None) if fname is None else fname if fname: self.dictionary.save(fname + '.dict') def load(self, fname): self.dictionary.load(fname + '.dict') def build_query_representation(self, query): """ Build representation of query, e.g. words or n-grams """ rep = {} rep['words'] = {} words = [w for w in self.dictionary.tokenize(query.lower())] rw = rep['words'] used = {} for w in words: if len(self.dictionary.freqs()) > 0: rw[w] = 1.0 / (1.0 + math.log(1.0 + self.dictionary.freqs()[w])) else: if w not in stopwords: rw[w] = 1 used[w] = True norm = len(used) rep['norm'] = math.sqrt(len(words)) return rep