Ejemplo n.º 1
0
 def __init__(self, conf):
     self.log = log
     self.extract_obj = EntityExtractor(conf)
     self.task_collector = EntityStatistics(conf['task_collect_db'])
     self.task_collector.start()
     if conf.get("use_old_deploy", False):
         self.deploy_method = DeployMethod.AFTER_EXTRACTOR
     else:
         self.deploy_method = DeployMethod.AFTER_MULTI_SRC
    def __init__(self):

        self.entity_extractor = EntityExtractor()
        self.encoder = Encoder()
        self.action_manipulator = ActionManipulator()
        self.config = Config()
        self.du = DataUtil()
        obs_size = self.config.u_embed_size + self.config.vocab_size + self.config.feature_size
        self.action_templates = self.action_manipulator.get_action_templates()
        self.model = Model()
Ejemplo n.º 3
0
    def __init__(self, base_url, geo_url, geo_threshold):
        self.graph = nx.Graph()
        self.nodes_detailed = {}
        self.geo_url = geo_url
        self.geo_threshold = geo_threshold
        self.sf = SentimentFilter()
        self.ent_ext = EntityExtractor()

        if base_url[-1] == '/':
            self.url = base_url
        else:
            self.url = base_url + '/'
Ejemplo n.º 4
0
def query():
    if request.method == 'POST':
        document = request.form['document']
        query_str = request.form['query']
        extractor = EntityExtractor(nlp(document), True)
        entities = extractor.TopEntities(query_str)
        return render_template('query.html',
                               document=document,
                               query=query_str,
                               entities=entities,
                               extractor=extractor)
    else:
        root()
    def test_news(self):
        with codecs.open('test_documents/text_document2.txt', 'rb', 'utf-8') as f:
            result = EntityExtractor().extract_entities(f.read())

            self.assertTrue(u'Donald Trump' in result['PERSON'])
            self.assertTrue(u'Brian France' in result['PERSON'])
            self.assertTrue(u'Howard Lorber' in result['PERSON'])
            self.assertTrue(u'Hillary Clinton' in result['PERSON'])
Ejemplo n.º 6
0
    def __init__(self):

        self.mention_extractor = MentionExtractor()
        self.property_extractor = PropertyExtractor()
        self.entity_extractor = EntityExtractor()
        self.tuple_extractor = TupleExtractor()

        self.topn_e = 5
        self.topn_t = 3

        path = '../data/model/entity_classifier_model.pkl'
        with open(path, 'rb') as f:
            self.entity_classifier = pickle.load(f)

        path = '../data/model/tuple_classifier_model.pkl'
        with open(path, 'rb') as f:
            self.tuple_classifier = pickle.load(f)

        self.scaler = joblib.load('../data/model/tuple_scaler')
    def test_url1(self):
        result = EntityExtractor().extract_entities_from_url('https://en.wikipedia.org/wiki/Todd_Hido')

        self.assertTrue(u'Todd Hido' in result['PERSON'])
        self.assertTrue(u'Alec Soth' in result['PERSON'])
        self.assertTrue(u'David Campany' in result['PERSON'])
        self.assertTrue(u'Stephen Shore' in result['PERSON'])
        self.assertTrue(u'Nan Goldin' in result['PERSON'])
        self.assertTrue(u'Rineke Dijkstra' in result['PERSON'])
        self.assertTrue(u'Larry Sultan' in result['PERSON'])
Ejemplo n.º 8
0
class EntityExtractorProccessor(NormalProccessor):

    def __init__(self, conf):
        self.log = log
        self.extract_obj = EntityExtractor(conf)
        self.task_collector = EntityStatistics(conf['task_collect_db'])
        self.task_collector.start()
        if conf.get("use_old_deploy", False):
            self.deploy_method = DeployMethod.AFTER_EXTRACTOR
        else:
            self.deploy_method = DeployMethod.AFTER_MULTI_SRC

    def to_string(self, link_info):
        str_entity = None
        try:
            tMemory_b = TMemoryBuffer()
            tBinaryProtocol_b = TBinaryProtocol(tMemory_b)
            link_info.write(tBinaryProtocol_b)
            str_entity = tMemory_b.getvalue()
        except:
            self.log.warning("cann't write EntityExtractorInfo to string")
        return str_entity

    def stop(self):
        self.task_collector.stop()

    def do_task_thrift(self, body):
        parse_info = PageParseInfo()
        try:
            tMemory_o = TMemoryBuffer(body)
            tBinaryProtocol_o = TBinaryProtocol(tMemory_o)
            parse_info.read(tBinaryProtocol_o)
            ret = self.extract_obj.entity_extractor(parse_info)
        except EOFError, e:
            self.log.warning("cann't read PageParseInfo from string")
            return None

        if int(ret.get('CODE', -10000)) < 0:
            return None

        entity_data_list = ret.get('LIST', [])
        msg_list = []
        if len(entity_data_list) > 0:
            for entity_data in entity_data_list:
                if isinstance(entity_data, EntityExtractorInfo):
                    msg_list.append(self.to_string(entity_data))

        else:
            return None
        self.log.info("Topic_id:%s\tsend_msg_num:%s" % (ret.get('TOPIC_ID'), len(msg_list)))
        return msg_list
Ejemplo n.º 9
0
class KBQA:
    def __init__(self):
        self.extractor = EntityExtractor()
        self.searcher = AnswerSearching()
        self.answer = "对不起,您的问题我不知道,我今后会努力改进的。"

    def qa_query(self, question):
        entities = self.extractor.extractor(question)
        if not entities:  # 没catch到任何实体
            return self.answer
        sqls = self.searcher.question_parser(entities)
        final_answer = self.searcher.searching(sqls)
        if not final_answer:
            return self.answer
        else:
            return '\n'.join(final_answer)
Ejemplo n.º 10
0
class KBQA:
    def __init__(self):
        self.extractor = EntityExtractor()
        self.searcher = AnswerSearching()

    def qa_main(self, input_str):
        answer = "对不起,您的问题我不知道,我今后会努力改进的。"
        entities = self.extractor.extractor(input_str)
        if not entities:
            return answer
        sqls = self.searcher.question_parser(entities)
        final_answer = self.searcher.searching(sqls)
        if not final_answer:
            return answer
        else:
            return '\n'.join(final_answer)
    def test_1(self):

        with codecs.open('test_documents/text_document1.txt', 'rb', 'utf-8') as f:
            result = EntityExtractor().extract_entities(f.read())

            self.assertTrue(u'Robert Adams' in result['PERSON'])
            self.assertTrue(u'Alfred Stieglitz' in result['PERSON'])
            self.assertTrue(u'Claude Cahun' in result['PERSON'])
            self.assertTrue(u'Hans Bellmer' in result['PERSON'])
            self.assertTrue(u'Edward Weston' in result['PERSON'])
            self.assertTrue(u'William Eggleston' in result['PERSON'])
            self.assertTrue(u'Lewis Baltz' in result['PERSON'])
            self.assertTrue(u'Diane Arbus' in result['PERSON'])
            self.assertTrue(u'Larry Sultan' in result['PERSON'])
            self.assertTrue(u'Charles Sheeler' in result['PERSON'])
            self.assertTrue(u'Rineke Dijkstra' in result['PERSON'])
            self.assertTrue(u'Man Ray' in result['PERSON'])
Ejemplo n.º 12
0
 def __init__(self):
     self.extractor = EntityExtractor()
     self.searcher = AnswerSearching()
Ejemplo n.º 13
0
class KBQA():
    def __init__(self):

        self.mention_extractor = MentionExtractor()
        self.property_extractor = PropertyExtractor()
        self.entity_extractor = EntityExtractor()
        self.tuple_extractor = TupleExtractor()

        self.topn_e = 5
        self.topn_t = 3

        path = '../data/model/entity_classifier_model.pkl'
        with open(path, 'rb') as f:
            self.entity_classifier = pickle.load(f)

        path = '../data/model/tuple_classifier_model.pkl'
        with open(path, 'rb') as f:
            self.tuple_classifier = pickle.load(f)

        self.scaler = joblib.load('../data/model/tuple_scaler')

    def add_answers_to_corpus(self, corpus):
        for sample in corpus:
            question = sample['question']
            ans = self.answer_main(question)
            sample['predict_ans'] = ans

        return corpus

    def answer_main(self, question):
        '''
        输入问题,依次执行:
        抽取实体mention、抽取属性值、生成候选实体并得到特征、候选实体过滤、
        生成候选查询路径(单实体双跳)、候选查询路径过滤
        使用top1的候选查询路径检索答案并返回
        input:
            question : python-str
        output:
            answer : python-list, [str]
        '''
        dic = {}
        print('>>> 原问题')
        print(question)

        question = re.sub('在金庸的小说《天龙八部》中,', '', question)
        question = re.sub('电视剧武林外传里', '', question)
        question = re.sub('《射雕英雄传》里', '', question)
        question = re.sub('情深深雨濛濛中', '', question)
        question = re.sub('《.+》中', '', question)
        question = re.sub('常青藤大学联盟中', '', question)
        question = re.sub('原名', '中文名', question)
        question = re.sub('英文', '外文', question)
        question = re.sub('英语', '外文', question)

        dic['question'] = question
        print('>>> re处理后的问题')
        print(question)

        mentions = self.mention_extractor.extract_mentions(question)
        dic['mentions'] = mentions
        print('>>> 实体mention抽取结果')
        print(list(mentions.keys()))

        properties = self.property_extractor.extract_properties(question)
        subject_properties, special_properties = self.add_properties(
            mentions, properties)
        dic['properties'] = subject_properties
        print('>>> 属性mention抽取结果')
        print(list(subject_properties.keys()))

        subjects = self.entity_extractor.extract_subject(
            mentions, subject_properties, question)
        dic['subjects'] = subjects
        print('>>> 主语实体')
        print(list(subjects.keys()))
        if len(subjects) == 0:
            return []

        subjects = self.subject_filter(subjects)
        for properties in special_properties:
            sub = '"' + properties + '"'
            if sub not in subjects:
                subjects[sub] = [special_properties[properties], 3, 1, 1, 2, 6]
        dic['subjects_filter'] = subjects
        print('>>> 筛选后的主语实体')
        print(list(subjects.keys()))
        if len(subjects) == 0:
            return []

        entity2relations = dict()
        for e in subjects:
            ret = get_relation_paths(e)
            entity2relations[e] = ret
        tuples = self.tuple_extractor.extract_tuples(subjects, question,
                                                     entity2relations)
        dic['tuples'] = tuples
        print('>>> 抽取的查询路径')
        print(list(tuples.keys()))
        if len(tuples) == 0:
            return []

        tuples = self.tuple_filter(tuples)
        dic['tuples_filter'] = tuples
        print('>>> 筛选后的查询路径')
        print(tuples)

        top_tuple = self.get_most_overlap_tuple(question, tuples)
        print('>>> 最终查询路径')
        print(top_tuple)

        # 生成查询语句
        search_paths = [each for each in top_tuple]
        if len(search_paths) == 2:
            sparql = 'select ?x where {{{a} {b} ?x}}'.format(\
                a=search_paths[0], b=search_paths[1])
            ans = get_answer(sparql)

        elif len(search_paths) == 3:
            sparql = 'select ?x where {{{a} {b} ?m . ?m {c} ?x}}'.format(\
                a=search_paths[0], b=search_paths[1], c=search_paths[2])
            ans = get_answer(sparql)

        elif len(search_paths) == 4:
            sparql = 'select ?x where {{{a} {b} ?x . ?x {c} {d}}}'.format(\
                a=search_paths[0], b=search_paths[1],
                c=search_paths[2], d=search_paths[3])
            ans = get_answer(sparql)
        else:
            print('不规范的查询路径')
            ans = []
        dic['answer'] = ans

        print('>>> 答案为')
        print(ans, '\n')

        return ans

    def add_properties(self, mentions, properties):
        '''
        mentions: {entity - mention} ???
        properties: {class - {entity - mention}}
        '''
        subject_properties = dict()
        subject_properties.update(properties['mark_properties'])
        subject_properties.update(properties['time_properties'])
        subject_properties.update(properties['digit_properties'])
        subject_properties.update(properties['other_properties'])
        subject_properties.update(properties['fuzzy_properties'])

        special_properties = dict()
        special_properties.update(properties['mark_properties'])
        special_properties.update(properties['time_properties'])

        return subject_properties, special_properties

    def subject_filter(self, subjects):
        '''
        输入候选主语和对应的特征,使用训练好的模型进行打分,排序后返回前topn个候选主语
        subjects: [entity - feature]
        '''
        entities, features = [], []
        for s in subjects:
            entities.append(s)
            features.append(subjects[s][1:])
        pred_prob = self.entity_classifier.predict_proba(
            np.array(features))[:, 1].tolist()
        sample_property = [each for each in zip(pred_prob, entities)]
        sample_property = sorted(sample_property,
                                 key=lambda x: x[0],
                                 reverse=True)
        entities = [each[1] for each in sample_property]
        pred_entities = entities[:self.topn_e]

        new_subjects = dict()
        for e in pred_entities:
            new_subjects[e] = subjects[e]
        return new_subjects

    def tuple_filter(self, tuples):
        '''
        输入候选答案和对应的特征,使用训练好的模型进行打分,排序后返回前topn个候选答案
        tuples: tuple - feature
        '''
        tuple_list, features = [], []
        for t in tuples:
            tuple_list.append(t)
            features.append(tuples[t][-1:])
        xxx = self.scaler.transform(features)
        pred_prob = self.tuple_classifier.predict_proba(xxx)[:, 1].tolist()

        sample_prop = [each for each in zip(pred_prob, tuple_list)]
        sample_prop = sorted(sample_prop, key=lambda x: x[0], reverse=True)
        tuples_sorted = [each[1] for each in sample_prop]
        return tuples_sorted[:self.topn_t]

    def get_most_overlap_tuple(self, question, tuples):
        '''
        从排名前几的tuples里选择与问题overlap最多的
        '''
        max_, ans = 0, tuples[0]
        for t in tuples:
            text = ''
            for element in t:
                element = element[1:-1].split('_')[0]
                text += element
            f1 = len(set(text).intersection(set(question)))
            f2 = f1 / len(set(text))
            f = f1 + f2
            if f > max_:
                max_, ans = f, t
        return ans
Ejemplo n.º 14
0
class Louvaine:
    def __init__(self, base_url, geo_url, geo_threshold):
        self.graph = nx.Graph()
        self.nodes_detailed = {}
        self.geo_url = geo_url
        self.geo_threshold = geo_threshold
        self.sf = SentimentFilter()
        self.ent_ext = EntityExtractor()

        if base_url[-1] == '/':
            self.url = base_url
        else:
            self.url = base_url + '/'

    def add_node(self, cluster):
        n_id = cluster['id']
        self.graph.add_node(n_id)
        self.nodes_detailed[n_id] = cluster

    def add_edge(self, c_link):
        self.graph.add_edge(c_link['source'], c_link['target'],
                            {'weight': c_link['weight']})

    def get_text_sum(self, cluster, r_o):
        n_posts = len(cluster['similar_post_ids'])
        l_sample = cluster['similar_post_ids']
        if n_posts > 30:
            l_sample = sample(cluster['similar_post_ids'], 30)
            n_posts = 30

        words = {}
        places = []
        websites = set([])
        r_o["campaigns"]["total"] += n_posts

        #TODO: fix query type once S.L. is fixed
        query_params = [{
            "query_type": "inq",
            "property_name": "post_id",
            "query_value": l_sample
        }]
        lp = Loopy(self.url + 'socialMediaPosts', query_params)
        page = lp.get_next_page()
        if page is None:
            return

        for doc in page:
            if doc['featurizer'] != cluster['data_type']:
                continue

            if 'campaigns' in doc:
                for cam in doc['campaigns']:
                    if cam in r_o["campaigns"]["ids"]:
                        r_o["campaigns"]["ids"][cam] += 1
                    else:
                        r_o["campaigns"]["ids"][cam] = 1

            locs = self.ent_ext.extract(doc['text'], tag='I-LOC')
            for loc in locs:
                print 'Location:', loc.encode('utf-8')
                try:
                    geos = Loopy.post(self.geo_url, json={'address': loc})
                    for place in geos:
                        places.append(place)
                        break
                except Exception as e:
                    print "error getting locations from geocoder...continuing.", e
                    traceback.print_exc()

            tokens = [
                w for w in self.sf.pres_tokenize(doc['text'], doc['lang'])
                if w not in stop_list
            ]
            for word in tokens:
                if word[0] == '#':
                    continue
                if word[0] == '@':
                    continue
                if word[:4] == 'http':
                    websites.add(word)
                    continue
                if word[:3] == 'www':
                    websites.add('http://' + word)
                    continue
                if word in words:
                    words[word] += 1
                else:
                    words[word] = 1

        for k, v in words.iteritems():
            k = remove_punctuation(k)
            if v < 5:
                continue
            if v in r_o['keywords']:
                r_o['keywords'][k] += v
            else:
                r_o['keywords'][k] = v

        for place in places:
            if type(place) is not dict:
                continue
            place_name = ''
            weight = 0.0
            if 'city' in place.keys():
                place_name = place['city'] + ' '
                weight += 1
            if 'state' in place.keys():
                place_name += place['state'] + ' '
                weight += .1
            if 'country' in place.keys():
                place_name += ' ' + place['country'] + ' '
                weight += .05
            if place_name in r_o['location']:
                r_o['location'][place_name]['weight'] += weight
            else:
                r_o['location'][place_name] = {
                    "type":
                    "inferred point",
                    "geo_type":
                    "point",
                    "coords": [{
                        "lat": place['latitude'],
                        "lng": place['longitude']
                    }],
                    "weight":
                    weight
                }

        r_o['location'] = dict((k, v) for k, v in r_o['location'].items()
                               if v['weight'] >= self.geo_threshold)

        for url in list(websites):
            r_o['urls'].add(url)

    def get_img_sum(self, cluster):
        n_posts = len(cluster['similar_post_ids'])
        l_sample = cluster['similar_post_ids']
        if n_posts > 100:
            l_sample = sample(cluster['similar_post_ids'], 100)

        imgs = set()

        #TODO: fix query type once S.L. is fixed
        for id in l_sample:
            query_params = [{
                "query_type": "between",
                "property_name": "post_id",
                "query_value": [id, id]
            }]
            lp = Loopy(self.url + 'socialMediaPosts', query_params)
            page = lp.get_next_page()
            if page is None:
                continue
            for doc in page:
                if 'primary_image_url' not in doc:
                    continue
                imgs.add(doc['primary_image_url'])
                break

        return imgs

    def get_communities(self):
        partition = community.best_partition(self.graph)
        d1 = {}

        print "Communities found, getting event summary information"
        n_nodes = len(self.graph.nodes())
        checkpoints = [.1, .25, .5, .75, .9, .95, .99, 1.1]
        ind_checked = 0
        n_checked = 0
        for n in self.graph.nodes():
            n_checked += 1
            while n_checked > checkpoints[ind_checked] * n_nodes:
                ind_checked += 1
                print "Finished {}% of nodes".format(
                    checkpoints[ind_checked - 1] * 100)

            images = set()
            com = str(partition[n])
            if n not in self.nodes_detailed:
                print "{} not found in detailed node list...why????".format(n)
                continue
            clust = self.nodes_detailed[n]
            if com in d1:
                d1[com]['cluster_ids'].append(n)
                d1[com]['topic_message_count'] += len(
                    clust['similar_post_ids'])
            else:
                d1[com] = {
                    'id': str(uuid.uuid4()),
                    'name': 'default',
                    'start_time_ms': clust['start_time_ms'],
                    'end_time_ms': clust['end_time_ms'],
                    'cluster_ids': [n],
                    'hashtags': {},
                    'keywords': {},
                    'campaigns': {
                        "total": 0,
                        'ids': {}
                    },
                    'urls': set([]),
                    'image_urls': [],
                    'location': {},
                    'importance_score': 1.0,
                    'topic_message_count': len(clust['similar_post_ids'])
                }

            #Expand Summary data (hashtags, keywords, images, urls, geo)
            if clust['data_type'] == 'hashtag':
                d1[com]['hashtags'][clust['term']] = len(
                    clust['similar_post_ids'])
                #Add full text analysis, many communities have no image/text nodes
                self.get_text_sum(clust, d1[com])
            elif clust['data_type'] == 'image':
                pass
            elif clust['data_type'] == 'text':
                self.get_text_sum(clust, d1[com])

            images |= self.get_img_sum(clust)

            d1[com]['image_urls'] = list(set(d1[com]['image_urls']) | images)

            #Make Sure Time is Correct
            if clust['start_time_ms'] < d1[com]['start_time_ms']:
                d1[com]['start_time_ms'] = clust['start_time_ms']
            if clust['end_time_ms'] > d1[com]['end_time_ms']:
                d1[com]['end_time_ms'] = clust['end_time_ms']

        print "Information collected, formatting output"

        #Cleanup -> transform dicst to order lists, sets to lists for easy javascript comprehension
        for com in d1.keys():
            l_camps = []
            if d1[com]['campaigns']['total'] != 0:
                l_camps = [{
                    k: 1. * v / float(d1[com]['campaigns']['total'])
                } for k, v in d1[com]['campaigns']['ids'].iteritems()]

            d1[com]['campaigns'] = l_camps

            # l_tags = map(lambda x: x[0], sorted([(k, v) for k, v in d1[com]['hashtags'].iteritems()], key=iget(1)))
            l_tags = sorted(list(d1[com]['hashtags'].iteritems()),
                            key=iget(1),
                            reverse=1)
            d1[com]['hashtags'] = l_tags[:100]  # slice

            # l_terms = map(lambda x: x[0], sorted([(k, v) for k, v in d1[com]['keywords'].iteritems()], key=lambda x: x[1]))
            l_terms = sorted(list(d1[com]['keywords'].iteritems()),
                             key=iget(1),
                             reverse=1)
            d1[com]['keywords'] = l_terms[:100]  # slice

            d1[com]['urls'] = list(d1[com]['urls'])

            temp = []
            for k, v in d1[com]['location'].iteritems():
                dt = v
                dt['label'] = k
                temp.append(dt)
            d1[com]['location'] = temp

        return d1
Ejemplo n.º 15
0
    def load_models(self, data_folder, models_folder, w2v_folder):
        self.logger.info(u'Loading models from {}'.format(models_folder))
        self.models_folder = models_folder

        self.premise_not_found = NoInformationModel()
        self.premise_not_found.load(models_folder, data_folder)

        # Загружаем общие параметры для сеточных моделей
        with open(os.path.join(models_folder, 'qa_model_selector.config'),
                  'r') as f:
            model_config = json.load(f)
            self.max_inputseq_len = model_config['max_inputseq_len']
            self.wordchar2vector_path = self.get_model_filepath(
                models_folder, model_config['wordchar2vector_path'])
            self.PAD_WORD = model_config['PAD_WORD']
            self.word_dims = model_config['word_dims']

        self.qa_model_config = model_config

        # TODO: выбор конкретной реализации для каждого типа моделей сделать внутри базового класса
        # через анализ поля 'engine' в конфигурации модели. Для нейросетевых моделей там будет
        # значение 'nn', для градиентного бустинга - 'xgb'. Таким образом, уберем ненужную связность
        # данного класса и конкретных реализации моделей.

        # Определение релевантности предпосылки и вопроса на основе XGB модели
        # self.relevancy_detector = XGB_RelevancyDetector()
        self.relevancy_detector = LGB_RelevancyDetector()
        # self.relevancy_detector = NN_RelevancyTripleLoss()
        self.relevancy_detector.load(models_folder)

        # Модель определения синонимичности двух фраз
        # self.synonymy_detector = NN_SynonymyDetector()
        # self.synonymy_detector = NN_SynonymyTripleLoss()
        self.synonymy_detector = LGB_SynonymyDetector()
        self.synonymy_detector.load(models_folder)
        # self.synonymy_detector = Jaccard_SynonymyDetector()

        self.interpreter = NN_Interpreter()
        self.interpreter.load(models_folder)

        self.req_interpretation = NN_ReqInterpretation()
        self.req_interpretation.load(models_folder)

        # Определение достаточности набора предпосылок для ответа на вопрос
        self.enough_premises = NN_EnoughPremisesModel()
        self.enough_premises.load(models_folder)

        # Комплексная модель (группа моделей) для генерации текста ответа
        self.answer_builder = AnswerBuilder()
        self.answer_builder.load_models(models_folder, self.text_utils)

        # Генеративная грамматика для формирования реплик
        self.replica_grammar = GenerativeGrammarEngine()
        with open(os.path.join(models_folder, 'replica_generator_grammar.bin'),
                  'rb') as f:
            self.replica_grammar = GenerativeGrammarEngine.unpickle_from(f)
        self.replica_grammar.set_dictionaries(self.text_utils.gg_dictionaries)

        # Классификатор грамматического лица на базе XGB
        #self.person_classifier = XGB_PersonClassifierModel()
        #self.person_classifier.load(models_folder)

        # Нейросетевая модель для манипуляции с грамматическим лицом
        #self.person_changer = NN_PersonChange()
        #self.person_changer.load(models_folder)

        # Модель определения модальности фраз собеседника
        self.modality_model = SimpleModalityDetectorRU()
        self.modality_model.load(models_folder)

        self.intent_detector = IntentDetector()
        self.intent_detector.load(models_folder)

        self.entity_extractor = EntityExtractor()
        self.entity_extractor.load(models_folder)

        # Загрузка векторных словарей
        self.word_embeddings = WordEmbeddings()
        self.word_embeddings.load_models(models_folder)
        self.word_embeddings.load_wc2v_model(self.wordchar2vector_path)
        for p in self.answer_builder.get_w2v_paths():
            p = os.path.join(w2v_folder, os.path.basename(p))
            self.word_embeddings.load_w2v_model(p)

        w2v_path = self.relevancy_detector.get_w2v_path()
        if w2v_path is not None:
            self.word_embeddings.load_w2v_model(w2v_path)

        if self.premise_not_found.get_w2v_path():
            self.word_embeddings.load_w2v_model(
                self.premise_not_found.get_w2v_path())

        self.word_embeddings.load_w2v_model(
            os.path.join(w2v_folder,
                         os.path.basename(
                             self.enough_premises.get_w2v_path())))

        self.jsyndet = Jaccard_SynonymyDetector()

        self.logger.debug('All models loaded')
Ejemplo n.º 16
0
class SimpleAnsweringMachine(BaseAnsweringMachine):
    """
    Движок чатбота на основе набора нейросетевых и прочих моделей (https://github.com/Koziev/chatbot).
    Методы класса реализуют workflow обработки реплик пользователя - формирование ответов, управление
    базой знаний.
    """
    def __init__(self, text_utils):
        super(SimpleAnsweringMachine, self).__init__()
        self.trace_enabled = False
        self.session_factory = SimpleDialogSessionFactory()
        self.text_utils = text_utils
        self.logger = logging.getLogger('SimpleAnsweringMachine')

        # Если релевантность факта к вопросу в БФ ниже этого порога, то факт не подойдет
        # для генерации ответа на основе факта.
        self.min_premise_relevancy = 0.6
        self.min_faq_relevancy = 0.7

    def get_model_filepath(self, models_folder, old_filepath):
        """
        Для внутреннего использования - корректирует абсолютный путь
        к файлам данных модели так, чтобы был указанный каталог.
        """
        _, tail = os.path.split(old_filepath)
        return os.path.join(models_folder, tail)

    def load_models(self, data_folder, models_folder, w2v_folder):
        self.logger.info(u'Loading models from {}'.format(models_folder))
        self.models_folder = models_folder

        self.premise_not_found = NoInformationModel()
        self.premise_not_found.load(models_folder, data_folder)

        # Загружаем общие параметры для сеточных моделей
        with open(os.path.join(models_folder, 'qa_model_selector.config'),
                  'r') as f:
            model_config = json.load(f)
            self.max_inputseq_len = model_config['max_inputseq_len']
            self.wordchar2vector_path = self.get_model_filepath(
                models_folder, model_config['wordchar2vector_path'])
            self.PAD_WORD = model_config['PAD_WORD']
            self.word_dims = model_config['word_dims']

        self.qa_model_config = model_config

        # TODO: выбор конкретной реализации для каждого типа моделей сделать внутри базового класса
        # через анализ поля 'engine' в конфигурации модели. Для нейросетевых моделей там будет
        # значение 'nn', для градиентного бустинга - 'xgb'. Таким образом, уберем ненужную связность
        # данного класса и конкретных реализации моделей.

        # Определение релевантности предпосылки и вопроса на основе XGB модели
        # self.relevancy_detector = XGB_RelevancyDetector()
        self.relevancy_detector = LGB_RelevancyDetector()
        # self.relevancy_detector = NN_RelevancyTripleLoss()
        self.relevancy_detector.load(models_folder)

        # Модель определения синонимичности двух фраз
        # self.synonymy_detector = NN_SynonymyDetector()
        # self.synonymy_detector = NN_SynonymyTripleLoss()
        self.synonymy_detector = LGB_SynonymyDetector()
        self.synonymy_detector.load(models_folder)
        # self.synonymy_detector = Jaccard_SynonymyDetector()

        self.interpreter = NN_Interpreter()
        self.interpreter.load(models_folder)

        self.req_interpretation = NN_ReqInterpretation()
        self.req_interpretation.load(models_folder)

        # Определение достаточности набора предпосылок для ответа на вопрос
        self.enough_premises = NN_EnoughPremisesModel()
        self.enough_premises.load(models_folder)

        # Комплексная модель (группа моделей) для генерации текста ответа
        self.answer_builder = AnswerBuilder()
        self.answer_builder.load_models(models_folder, self.text_utils)

        # Генеративная грамматика для формирования реплик
        self.replica_grammar = GenerativeGrammarEngine()
        with open(os.path.join(models_folder, 'replica_generator_grammar.bin'),
                  'rb') as f:
            self.replica_grammar = GenerativeGrammarEngine.unpickle_from(f)
        self.replica_grammar.set_dictionaries(self.text_utils.gg_dictionaries)

        # Классификатор грамматического лица на базе XGB
        #self.person_classifier = XGB_PersonClassifierModel()
        #self.person_classifier.load(models_folder)

        # Нейросетевая модель для манипуляции с грамматическим лицом
        #self.person_changer = NN_PersonChange()
        #self.person_changer.load(models_folder)

        # Модель определения модальности фраз собеседника
        self.modality_model = SimpleModalityDetectorRU()
        self.modality_model.load(models_folder)

        self.intent_detector = IntentDetector()
        self.intent_detector.load(models_folder)

        self.entity_extractor = EntityExtractor()
        self.entity_extractor.load(models_folder)

        # Загрузка векторных словарей
        self.word_embeddings = WordEmbeddings()
        self.word_embeddings.load_models(models_folder)
        self.word_embeddings.load_wc2v_model(self.wordchar2vector_path)
        for p in self.answer_builder.get_w2v_paths():
            p = os.path.join(w2v_folder, os.path.basename(p))
            self.word_embeddings.load_w2v_model(p)

        w2v_path = self.relevancy_detector.get_w2v_path()
        if w2v_path is not None:
            self.word_embeddings.load_w2v_model(w2v_path)

        if self.premise_not_found.get_w2v_path():
            self.word_embeddings.load_w2v_model(
                self.premise_not_found.get_w2v_path())

        self.word_embeddings.load_w2v_model(
            os.path.join(w2v_folder,
                         os.path.basename(
                             self.enough_premises.get_w2v_path())))

        self.jsyndet = Jaccard_SynonymyDetector()

        self.logger.debug('All models loaded')

    def extract_entity(self, entity_name, phrase_str):
        return self.entity_extractor.extract_entity(entity_name, phrase_str,
                                                    self.text_utils,
                                                    self.word_embeddings)

    def start_conversation(self, bot, interlocutor):
        """
        Начало общения бота с interlocutor. Ни одной реплики еще не было.
        Бот может поприветствовать собеседника или напомнить ему что-то, если
        в сессии с ним была какая-то напоминалка, т.д. Фразу, которую надо показать собеседнику,
        поместим в буфер выходных фраз с помощью метода say, а внешний цикл обработки уже извлечет ее оттуда
        и напечатает в консоли и т.д.

        :param bot: экземпляр класса BotPersonality
        :param interlocutor: строковый идентификатор собеседника.
        :return: строка реплики, которую скажет бот.
        """
        session = self.get_session(bot, interlocutor)
        if bot.has_scripting():
            phrase = bot.scripting.start_conversation(self, session)
            if phrase is not None:
                self.say(session, phrase)

    def get_session_factory(self):
        return self.session_factory

    def is_question(self, phrase):
        modality, person = self.modality_model.get_modality(
            phrase, self.text_utils, self.word_embeddings)
        return modality == ModalityDetector.question

    def translate_interlocutor_replica(self, bot, session, raw_phrase):
        rules = bot.get_comprehension_templates().get_templates()
        order2anchor = dict((order, anchor) for (anchor, order) in rules)
        phrases = list(order for (anchor, order) in rules)
        phrases2 = list((self.text_utils.wordize_text(order), None, None)
                        for (anchor, order) in rules)
        canonized2raw = dict(
            (f2[0], f1) for (f1, f2) in itertools.izip(phrases, phrases2))

        raw_phrase2 = self.text_utils.wordize_text(raw_phrase)
        best_order, best_sim = self.synonymy_detector.get_most_similar(
            raw_phrase2,
            phrases2,
            self.text_utils,
            self.word_embeddings,
            nb_results=1)

        # Если похожесть проверяемой реплики на любой вариант в таблице приказов выше порога,
        # то дальше будем обрабатывать нормализованную фразу вместо исходной введенной.
        comprehension_threshold = 0.70
        if best_sim > comprehension_threshold:
            if self.trace_enabled:
                self.logger.info(
                    u'Closest comprehension phrase is "{}" with similarity={} above threshold={}'
                    .format(best_order, best_sim, comprehension_threshold))

            interpreted_order = order2anchor[canonized2raw[best_order]]
            if raw_phrase2 != interpreted_order:
                if self.trace_enabled:
                    self.logger.info(
                        u'Phrase "{}" is interpreted as "{}"'.format(
                            raw_phrase, interpreted_order))
                return interpreted_order
            else:
                return None
        else:
            return None

    def interpret_phrase(self, bot, session, raw_phrase, internal_issuer):
        interpreted = InterpretedPhrase(raw_phrase)
        phrase = raw_phrase
        phrase_modality, phrase_person = self.modality_model.get_modality(
            phrase, self.text_utils, self.word_embeddings)
        phrase_is_question = phrase_modality == ModalityDetector.question

        if self.intent_detector is not None:
            interpreted.intent = self.intent_detector.detect_intent(
                raw_phrase, self.text_utils, self.word_embeddings)
            self.logger.debug(u'intent="%s"', interpreted.intent)

        # история фраз доступна в session как conversation_history
        was_interpreted = False

        last_phrase = session.conversation_history[-1] if len(
            session.conversation_history) > 0 else None

        if not internal_issuer:
            # Интерпретация вопроса собеседника (человека):
            # (H) Ты яблоки любишь?
            # (B) Да
            # (H) А виноград? <<----- == Ты виноград любишь?
            if len(session.conversation_history) > 1 and phrase_is_question:
                last2_phrase = session.conversation_history[
                    -2]  # это вопрос человека "Ты яблоки любишь?"

                if not last2_phrase.is_bot_phrase\
                    and last2_phrase.is_question\
                    and self.interpreter is not None:

                    if self.req_interpretation.require_interpretation(
                            raw_phrase, self.text_utils, self.word_embeddings):
                        context_phrases = list()
                        # Контекст состоит из двух предыдущих фраз
                        context_phrases.append(last2_phrase.raw_phrase)
                        context_phrases.append(last_phrase.raw_phrase)
                        context_phrases.append(raw_phrase)
                        phrase = self.interpreter.interpret(
                            context_phrases, self.text_utils,
                            self.word_embeddings)
                        was_interpreted = True

            # and last_phrase.is_question\
            if not was_interpreted\
                    and len(session.conversation_history) > 0\
                    and last_phrase.is_bot_phrase\
                    and not phrase_is_question\
                    and self.interpreter is not None:

                if self.req_interpretation.require_interpretation(
                        raw_phrase, self.text_utils, self.word_embeddings):
                    # В отдельной ветке обрабатываем ситуацию, когда бот
                    # задал вопрос или квази-вопрос типа "А давай xxx", на который собеседник дал краткий ответ.
                    # с помощью специальной модели мы попробуем восстановить полный
                    # текст ответа собеседника.
                    context_phrases = list()
                    context_phrases.append(last_phrase.interpretation)
                    context_phrases.append(raw_phrase)
                    phrase = self.interpreter.interpret(
                        context_phrases, self.text_utils, self.word_embeddings)
                    was_interpreted = True

        if was_interpreted:
            phrase = self.interpreter.normalize_person(phrase, self.text_utils,
                                                       self.word_embeddings)

        if not internal_issuer:
            # Попробуем найти шаблон трансляции, достаточно похожий на эту фразу.
            # Может получиться так, что введенная императивная фраза станет обычным вопросом:
            # "назови свое имя!" ==> "Как тебя зовут?"
            translated_str = self.translate_interlocutor_replica(
                bot, session, phrase)
            if translated_str is not None:
                phrase = translated_str
                raw_phrase = translated_str
                phrase_modality, phrase_person = self.modality_model.get_modality(
                    phrase, self.text_utils, self.word_embeddings)
                was_interpreted = True

        if not was_interpreted:
            phrase = self.interpreter.normalize_person(raw_phrase,
                                                       self.text_utils,
                                                       self.word_embeddings)

        # TODO: Если результат интерпретации содержит мусор, то не нужно его обрабатывать.
        # Поэтому тут надо проверить phrase  с помощью верификатора синтаксиса.
        # ...

        interpreted.interpretation = phrase
        interpreted.set_modality(phrase_modality, phrase_person)

        return interpreted

    def say(self, session, answer):
        self.logger.info(u'say "%s"', answer)
        answer_interpretation = InterpretedPhrase(answer)
        answer_interpretation.is_bot_phrase = True
        answer_interpretation.set_modality(*self.modality_model.get_modality(
            answer, self.text_utils, self.word_embeddings))
        session.add_to_buffer(answer)
        session.add_phrase_to_history(answer_interpretation)

    def does_bot_know_answer(self, question, session):
        """Вернет true, если бот знает ответ на вопрос question"""
        # TODO
        return False

    def calc_discourse_relevance(self, replica, session):
        """Возвращает оценку соответствия реплики replica текущему дискурсу беседы session"""
        # TODO
        return 1.0

    def push_phrase(self,
                    bot,
                    interlocutor,
                    phrase,
                    internal_issuer=False,
                    force_question_answering=False):
        self.logger.info(u'push_phrase interlocutor="%s" phrase="%s"',
                         interlocutor, phrase)
        question = self.text_utils.canonize_text(phrase)
        if question == u'#traceon':
            self.trace_enabled = True
            return
        elif question == u'#traceoff':
            self.trace_enabled = False
            return
        elif question == u'#facts':
            for fact, person, fact_id in bot.facts.enumerate_facts(
                    interlocutor):
                print(u'{}'.format(fact))
            return

        session = self.get_session(bot, interlocutor)

        # Выполняем интерпретацию фразы с учетом ранее полученных фраз,
        # так что мы можем раскрыть анафору, подставить в явном виде опущенные составляющие и т.д.,
        # определить, является ли фраза вопросом, фактом или императивным высказыванием.
        interpreted_phrase = self.interpret_phrase(bot, session, question,
                                                   internal_issuer)

        if force_question_answering:
            # В случае, если наш бот должен считать все входные фразы вопросами,
            # на которые он должен отвечать.
            interpreted_phrase.set_modality(ModalityDetector.question,
                                            interpreted_phrase.person)

        # Утверждения для 2го лица, то есть относящиеся к профилю чатбота, будем
        # рассматривать как вопросы. Таким образом, запрещаем прямой вербальный
        # доступ к профилю чатбота на запись.
        is_question2 = interpreted_phrase.is_assertion and interpreted_phrase.person == 2

        # Интерпретация фраз и в общем случае реакция на них зависит и от истории
        # общения, поэтому результат интерпретации сразу добавляем в историю.
        session.add_phrase_to_history(interpreted_phrase)

        if interpreted_phrase.is_imperative:
            self.logger.debug(u'Processing as imperative: "%s"',
                              interpreted_phrase.interpretation)
            # Обработка приказов (императивов).
            order_processed = self.process_order(bot, session, interlocutor,
                                                 interpreted_phrase)
            if not order_processed:
                # Сообщим, что не знаем как обработать приказ.
                self.premise_not_found_model.order_not_understood(
                    phrase, bot, self.text_utils, self.word_embeddings)
                order_processed = True
        elif interpreted_phrase.is_question or is_question2:
            self.logger.debug(u'Processing as question: "%s"',
                              interpreted_phrase.interpretation)
            # Обрабатываем вопрос собеседника (либо результат трансляции императива).
            answers = self.build_answers(session, bot, interlocutor,
                                         interpreted_phrase)
            for answer in answers:
                self.say(session, answer)

            # Возможно, кроме ответа на вопрос, надо выдать еще какую-то реплику.
            # Например, для смены темы разговора.
            if len(answers) > 0:
                if False:  #bot.has_scripting():
                    additional_speech = bot.scripting.generate_after_answer(
                        bot, self, interlocutor, interpreted_phrase,
                        answers[-1])
                    if additional_speech is not None:
                        self.say(session, additional_speech)
        else:
            self.logger.debug(u'Processing as assertion: "%s"',
                              interpreted_phrase.interpretation)

            # Обработка прочих фраз. Обычно это просто утверждения (новые факты, болтовня).
            # Пробуем применить общие правила, которые опираются в том числе на
            # intent реплики или ее текст.
            input_processed = bot.apply_rule(session, interlocutor,
                                             interpreted_phrase)

            # TODO: в принципе возможны два варианты последствий срабатывания
            # правил. 1) считаем, что правило полностью выполнило все действия для
            # утверждения, в том числе сохранило в базе знаний новый факт, если это
            # необходимо. 2) полагаем, что правило что-то сделало, но факт в базу мы должны
            # добавить сами.
            # Возможно, надо явно задавать в правилах эти особенности (INSTEAD-OF или BEFORE)
            # Пока считаем, что правило сделало все, что требовалось.

            answer_generated = False
            answer = None

            if not input_processed:
                # Утверждение добавляем как факт в базу знаний, в раздел для
                # текущего собеседника.
                # TODO: факты касательно третьих лиц надо вносить в общий раздел базы, а не
                # для текущего собеседника.
                fact_person = '3'
                fact = interpreted_phrase.interpretation
                if self.trace_enabled:
                    logging.info(u'Adding "%s" to knowledge base', fact)
                bot.facts.store_new_fact(
                    interlocutor, (fact, fact_person, '--from dialogue--'))

                generated_replicas = [
                ]  # список кортежей (подобранная_реплика_бота, вес_реплики)

                if bot.enable_smalltalk and bot.has_scripting():
                    # подбираем подходящую реплику в ответ на не-вопрос собеседника (обычно это
                    # ответ на наш вопрос, заданный ранее).
                    smalltalk_rules = bot.get_scripting(
                    ).enumerate_smalltalk_rules()

                    interlocutor_phrases = session.get_interlocutor_phrases(
                        questions=False, assertions=True)
                    for phrase, timegap in interlocutor_phrases[:
                                                                1]:  # 05.06.2019 берем одну последнюю фразу
                        best_premise, best_rel = self.synonymy_detector.get_most_similar(
                            phrase, [(item.get_condition_text(), -1, -1)
                                     for item in smalltalk_rules],
                            self.text_utils, self.word_embeddings)
                        time_decay = math.exp(
                            -timegap
                        )  # штрафуем фразы, найденные для более старых реплик

                        if best_rel > 0.7:
                            for item in smalltalk_rules:
                                if item.get_condition_text() == best_premise:

                                    # Используем это правило для генерации реплики.
                                    # Правило может быть простым, с явно указанной фразой, либо
                                    # содержать набор шаблонов генерации.

                                    if item.is_generator():
                                        # Используем скомпилированную грамматику для генерации фраз..
                                        words = phrase.split()
                                        all_generated_phrases = item.compiled_grammar.generate(
                                            words, self.text_utils.known_words)
                                        if len(all_generated_phrases) > 0:
                                            # Уберем вопросы, которые мы уже задавали, оставим top
                                            top = sorted(all_generated_phrases,
                                                         key=lambda z: -z.
                                                         get_rank())[:50]
                                            top = filter(
                                                lambda z: session.
                                                count_bot_phrase(z.get_str()
                                                                 ) == 0, top)

                                            # Выберем рандомно одну из фраз
                                            px = [z.get_rank() for z in top]
                                            sum_p = sum(px)
                                            px = [p / sum_p for p in px]
                                            best = np.random.choice(top,
                                                                    1,
                                                                    p=px)[0]
                                            replica = best.get_str()

                                            discourse_rel = self.calc_discourse_relevance(
                                                replica, session)

                                            if self.is_question(replica):
                                                # бот не должен задавать вопрос, если он уже знает на него ответ.
                                                if not self.does_bot_know_answer(
                                                        replica, session):
                                                    generated_replicas.append(
                                                        (replica,
                                                         best.get_rank() *
                                                         discourse_rel *
                                                         time_decay, 'debug3'))
                                            else:
                                                generated_replicas.append(
                                                    (replica, best.get_rank() *
                                                     discourse_rel *
                                                     time_decay, 'debug4'))

                                    else:
                                        # Текст формируемой реплики указан буквально.
                                        # Следует учесть, что ответные реплики в SmalltalkReplicas могут быть ненормализованы,
                                        # поэтому их следует сначала нормализовать.
                                        for replica in item.answers:
                                            # Такой вопрос не задавался недавно?
                                            if session.count_bot_phrase(
                                                    replica) == 0:
                                                # нужно учесть соответствие этой реплики replica текущему дискурсу
                                                # беседы... Например, можно учесть максимальную похожесть на N последних
                                                # реплик...
                                                discourse_rel = self.calc_discourse_relevance(
                                                    replica, session)

                                                if self.is_question(replica):
                                                    # бот не должен задавать вопрос, если он уже знает на него ответ.
                                                    if not self.does_bot_know_answer(
                                                            replica, session):
                                                        generated_replicas.append(
                                                            (replica,
                                                             best_rel *
                                                             discourse_rel *
                                                             time_decay,
                                                             'debug1'))
                                                else:
                                                    generated_replicas.append(
                                                        (replica, best_rel *
                                                         discourse_rel *
                                                         time_decay, 'debug2'))

                                    break
                        else:
                            # Проверяем smalltalk-правила, использующие intent фразы
                            intent_rule_applied = False
                            for item in bot.get_scripting(
                            ).enumerate_smalltalk_intent_rules():
                                if item.condition_text == interpreted_phrase.intent:
                                    intent_rule_applied = True
                                    if item.is_generator():
                                        # Используем скомпилированную грамматику для генерации фраз..
                                        words = phrase.split()
                                        all_generated_phrases = item.compiled_grammar.generate(
                                            words, self.text_utils.known_words)
                                        if len(all_generated_phrases) > 0:
                                            # Уберем вопросы, которые мы уже задавали, оставим top
                                            top = sorted(all_generated_phrases,
                                                         key=lambda z: -z.
                                                         get_rank())[:50]
                                            top = filter(
                                                lambda z: session.
                                                count_bot_phrase(z.get_str()
                                                                 ) == 0, top)

                                            # Выберем рандомно одну из фраз
                                            px = [z.get_rank() for z in top]
                                            sum_p = sum(px)
                                            px = [p / sum_p for p in px]
                                            best = np.random.choice(top,
                                                                    1,
                                                                    p=px)[0]
                                            replica = best.get_str()

                                            discourse_rel = self.calc_discourse_relevance(
                                                replica, session)

                                            if self.is_question(replica):
                                                # бот не должен задавать вопрос, если он уже знает на него ответ.
                                                if not self.does_bot_know_answer(
                                                        replica, session):
                                                    generated_replicas.append(
                                                        (replica,
                                                         best.get_rank() *
                                                         discourse_rel *
                                                         time_decay, 'debug3'))
                                            else:
                                                generated_replicas.append(
                                                    (replica, best.get_rank() *
                                                     discourse_rel *
                                                     time_decay, 'debug4'))

                                    break

                            if not intent_rule_applied:
                                # TODO: вероятность использования разных способом генерации реплик вынести
                                # в общие настройки бота

                                if random.random() > 0.5:
                                    phrases = [
                                        (f, )
                                        for f in bot.get_common_phrases()
                                        if f != phrase
                                    ]

                                    sim_phrases = self.jsyndet.get_most_similar(
                                        phrase,
                                        phrases,
                                        self.text_utils,
                                        self.word_embeddings,
                                        nb_results=1)
                                    for f, phrase_sim in [sim_phrases]:
                                        if phrase_sim > 0.10 and f.lower(
                                        ) != phrase:
                                            if session.count_bot_phrase(
                                                    f) == 0:
                                                # проверить, если answer является репликой-ответом: знает
                                                # ли бот ответ на этот вопрос.
                                                #if self.is_question(replica):
                                                #    if not self.does_bot_know_answer(replica, session):
                                                #        generated_replicas.append((replica, replica_rel))

                                                discourse_rel = self.calc_discourse_relevance(
                                                    f, session)
                                                generated_replicas.append(
                                                    (f, phrase_sim *
                                                     discourse_rel *
                                                     time_decay,
                                                     'common_phrases'))

                                    # Выбираем ближайший факт
                                    facts = bot.facts.enumerate_facts(
                                        interlocutor)
                                    facts = [
                                        fact for fact in facts
                                        if fact[0].lower() != phrase
                                    ]
                                    sim_facts = self.jsyndet.get_most_similar(
                                        phrase,
                                        facts,
                                        self.text_utils,
                                        self.word_embeddings,
                                        nb_results=1)
                                    for fact, fact_sim in [sim_facts]:
                                        if fact_sim > 0.20 and fact.lower(
                                        ) != phrase:
                                            if session.count_bot_phrase(
                                                    fact) == 0:
                                                discourse_rel = self.calc_discourse_relevance(
                                                    fact, session)
                                                generated_replicas.append(
                                                    (fact,
                                                     fact_sim * discourse_rel *
                                                     time_decay,
                                                     'nesrest_fact'))
                                else:
                                    # Используем генеративную грамматику для получения возможных реплик
                                    logging.debug(
                                        'Using replica_grammar to generate replicas...'
                                    )
                                    words = self.text_utils.tokenize(phrase)
                                    all_generated_phrases = self.replica_grammar.generate(
                                        words,
                                        self.text_utils.known_words,
                                        use_assocs=False)

                                    for replica in sorted(
                                            all_generated_phrases,
                                            key=lambda z: -z.get_rank())[:5]:
                                        replica_str = replica.get_str()
                                        # Такой реплики еще не было в истории?
                                        if session.count_bot_phrase(
                                                replica_str) == 0:
                                            discourse_rel = self.calc_discourse_relevance(
                                                replica_str, session)
                                            # TODO - добавить сюда еще взвешивание по модели синтаксической валидации
                                            replica_w = discourse_rel * replica.get_rank(
                                            )
                                            #print(u'{:6f}\t{}'.format(phrase.get_rank(), phrase.get_str()))
                                            generated_replicas.append(
                                                (replica.get_str(), replica_w,
                                                 'replica_grammar'))
                                            break

                    # пробуем найти среди вопросов, которые задавал человек-собеседник недавно,
                    # максимально близкие к вопросам в smalltalk базе.
                    if False:
                        smalltalk_utterances = set()
                        for item in smalltalk_phrases:
                            smalltalk_utterances.update(item.answers)

                        interlocutor_phrases = session.get_interlocutor_phrases(
                            questions=True, assertions=False)
                        for phrase, timegap in interlocutor_phrases:
                            # Ищем ближайшие реплики для данной реплики человека phrase
                            similar_items = self.synonymy_detector.get_most_similar(
                                phrase,
                                [(s, -1, -1) for s in smalltalk_utterances],
                                self.text_utils,
                                self.word_embeddings,
                                nb_results=5)
                            for replica, rel in similar_items:
                                if session.count_bot_phrase(replica) == 0:
                                    time_decay = math.exp(-timegap)
                                    generated_replicas.append(
                                        (replica, rel * 0.9 * time_decay,
                                         'debug3'))

                    # Теперь среди подобранных реплик бота в generated_replicas выбираем
                    # одну, учитывая их вес.
                    if len(generated_replicas) > 0:
                        replica_px = [z[1] for z in generated_replicas]
                        replicas = list(
                            map(operator.itemgetter(0), generated_replicas))
                        sum_p = sum(replica_px)  # +1e-7
                        replica_px = [p / sum_p for p in replica_px]
                        answer = np.random.choice(replicas, p=replica_px)
                        answer_generated = True
                    else:
                        answer_generated = False

            if answer_generated:
                self.say(session, answer)

    def process_order(self, bot, session, interlocutor, interpreted_phrase):
        self.logger.debug(u'Processing order "%s"',
                          interpreted_phrase.interpretation)

        # Пробуем применить общие правила, которые опираются в том числе на
        # intent реплики или ее текст.
        order_processed = bot.apply_rule(session, interlocutor,
                                         interpreted_phrase)
        if order_processed:
            return True
        else:
            return bot.process_order(session, interlocutor, interpreted_phrase)

    def apply_rule(self, bot, session, interpreted_phrase):
        return bot.apply_rule(session, interpreted_phrase)

    def premise_not_found(self, phrase, bot, text_utils, word_embeddings):
        return self.premise_not_found_model.generate_answer(
            phrase, bot, text_utils, word_embeddings)

    def build_answers0(self, session, bot, interlocutor, interpreted_phrase):
        if self.trace_enabled:
            self.logger.debug(u'Question to process="%s"',
                              interpreted_phrase.interpretation)

        # Проверяем базу FAQ, вдруг там есть развернутый ответ на вопрос.
        best_faq_answer = None
        best_faq_rel = 0.0
        best_faq_question = None
        if bot.faq:
            best_faq_answer, best_faq_rel, best_faq_question = bot.faq.get_most_similar(
                interpreted_phrase.interpretation, self.synonymy_detector,
                self.word_embeddings, self.text_utils)

        answers = []
        answer_rels = []
        best_rels = None

        # Нужна ли предпосылка, чтобы ответить на вопрос?
        # Используем модель, которая вернет вероятность того, что
        # пустой список предпосылок достаточен.
        p_enough = self.enough_premises.is_enough(
            premise_str_list=[],
            question_str=interpreted_phrase.interpretation,
            text_utils=self.text_utils,
            word_embeddings=self.word_embeddings)
        if p_enough > 0.5:
            # Единственный ответ можно построить без предпосылки, например для вопроса "Сколько будет 2 плюс 2?"
            answers, answer_rels = self.answer_builder.build_answer_text(
                [u''], [1.0], interpreted_phrase.interpretation,
                self.text_utils, self.word_embeddings)
            if len(answers) != 1:
                self.logger.debug(
                    u'Exactly 1 answer is expected for question={}, got {}'.
                    format(interpreted_phrase.interpretation, len(answers)))

            best_rels = answer_rels
        else:
            # определяем наиболее релевантную предпосылку
            memory_phrases = list(bot.facts.enumerate_facts(interlocutor))

            best_premises, best_rels = self.relevancy_detector.get_most_relevant(
                interpreted_phrase.interpretation,
                memory_phrases,
                self.text_utils,
                self.word_embeddings,
                nb_results=3)
            if self.trace_enabled:
                if best_rels[0] >= self.min_premise_relevancy:
                    self.logger.info(u'Best premise is "%s" with relevancy=%f',
                                     best_premises[0], best_rels[0])

            if len(answers) == 0:
                if bot.premise_is_answer:
                    if best_rels[0] >= self.min_premise_relevancy:
                        # В качестве ответа используется весь текст найденной предпосылки.
                        answers = [best_premises[:1]]
                        answer_rels = [best_rels[:1]]
                else:
                    premises2 = []
                    premise_rels2 = []

                    # 30.11.2018 будем использовать только 1 предпосылку и генерировать 1 ответ
                    if True:
                        if best_rels[0] >= self.min_premise_relevancy:
                            premises2 = [best_premises[:1]]
                            premise_rels2 = best_rels[:1]
                    else:
                        max_rel = max(best_rels)
                        for premise, rel in itertools.izip(
                                best_premises[:1], best_rels[:1]):
                            if rel >= self.min_premise_relevancy and rel >= 0.4 * max_rel:
                                premises2.append([premise])
                                premise_rels2.append(rel)

                    if len(premises2) > 0:
                        # генерация ответа на основе выбранной предпосылки.
                        answers, answer_rels = self.answer_builder.build_answer_text(
                            premises2, premise_rels2,
                            interpreted_phrase.interpretation, self.text_utils,
                            self.word_embeddings)

        if len(best_rels) == 0 or (best_faq_rel > best_rels[0]
                                   and best_faq_rel > self.min_faq_relevancy):
            # Если FAQ выдал более достоверный ответ, чем генератор ответа, или если
            # генератор ответа вообще ничего не выдал (в базе фактов пусто), то берем
            # тест ответа из FAQ.
            answers = [best_faq_answer]
            answer_rels = [best_faq_rel]
            self.logger.info(
                u'FAQ entry provides nearest question="%s" with rel=%e',
                best_faq_question, best_faq_rel)

        if len(answers) == 0:
            # Не удалось найти предпосылку для формирования ответа.

            # Попробуем обработать вопрос правилами.
            if not bot.apply_rule(session, interlocutor, interpreted_phrase):
                # Правила не сработали, значит выдаем реплику "Информации нет"
                answer = self.premise_not_found.generate_answer(
                    interpreted_phrase.interpretation, bot, self.text_utils,
                    self.word_embeddings)
                answers.append(answer)
                answer_rels.append(1.0)

        return answers, answer_rels

    def build_answers(self, session, bot, interlocutor, interpreted_phrase):
        answers, answer_confidenses = self.build_answers0(
            session, bot, interlocutor, interpreted_phrase)
        if len(answer_confidenses
               ) == 0 or max(answer_confidenses) < self.min_premise_relevancy:
            # тут нужен алгоритм генерации ответа в условиях, когда
            # у бота нет нужных фактов. Это может быть как ответ "не знаю",
            # так и вариант "нет" для определенных категорий вопросов.
            if False:  #bot.has_scripting():
                answer = bot.scripting.buid_answer(self, interlocutor,
                                                   interpreted_phrase)
                answers = [answer]

        return answers

    def pop_phrase(self, bot, interlocutor):
        session = self.get_session(bot, interlocutor)
        return session.extract_from_buffer()

    def get_session(self, bot, interlocutor):
        return self.session_factory.get_session(bot, interlocutor)

    def get_synonymy_detector(self):
        return self.synonymy_detector

    def get_text_utils(self):
        return self.text_utils

    def get_word_embeddings(self):
        return self.word_embeddings
class InteractiveSession():
    def __init__(self):

        self.entity_extractor = EntityExtractor()
        self.encoder = Encoder()
        self.action_manipulator = ActionManipulator()
        self.config = Config()
        self.du = DataUtil()
        obs_size = self.config.u_embed_size + self.config.vocab_size + self.config.feature_size
        self.action_templates = self.action_manipulator.get_action_templates()
        self.model = Model()
        # self.model.restore()

    def interact(self):
        # self.net.reset_state()
        while True:
            u = input(':: ')

            # check if user wants to begin new session
            if u == 'clear' or u == 'reset' or u == 'restart':
                # self.model.reset_state()
                # et = EntityTracker()
                # at = ActionTracker(et)
                print('')

            # check for exit command
            elif u == 'exit' or u == 'stop' or u == 'quit' or u == 'q':
                break

            else:
                # ENTER press : silence
                if not u:
                    u = '<SILENCE>'

                # encode
                u_ent, u_entities = self.entity_extractor.extract_entities(u)
                u_ent_features = et.context_features()

                u_emb = self.emb.encode(u)
                u_bow = self.bow_enc.encode(u)
                # concat features
                features = np.concatenate((u_ent_features, u_emb, u_bow),
                                          axis=0)
                # get action mask
                action_mask = at.action_mask()

                # forward
                prediction = self.net.forward(features, action_mask)
                print('prediction : ', prediction)
                print(u_entities)
                print('\n')
                if self.post_process(prediction, u_ent_features):
                    print(
                        '>>', 'api_call ' + u_entities['<cuisine>'] + ' ' +
                        u_entities['<location>'] + ' ' +
                        u_entities['<party_size>'] + ' ' +
                        u_entities['<rest_type>'])

                else:
                    prediction = self.action_post_process(
                        prediction, u_entities)
                    print('>>', self.action_templates[prediction])

                    # if all entities is satisfied and the user agree to make a reservation.
                    if all(u_ent_featur == 1
                           for u_ent_featur in u_ent_features) and (prediction
                                                                    == 10):
                        break

    def post_process(self, prediction, u_ent_features):
        if prediction == 0:
            return True
        attr_list = [9, 12, 6, 1]
        if all(u_ent_featur == 1
               for u_ent_featur in u_ent_features) and prediction in attr_list:
            return True
        else:
            return False

    def action_post_process(self, prediction, u_entities):
        attr_mapping_dict = {
            9: '<cuisine>',
            12: '<location>',
            6: '<party_size>',
            1: '<rest_type>'
        }

        # find exist and non-exist entity
        exist_ent_index = [
            key for key, value in u_entities.items() if value != None
        ]
        non_exist_ent_index = [
            key for key, value in u_entities.items() if value == None
        ]

        # if predicted key is already in exist entity index then find non exist entity index
        # and leads the user to input non exist entity.

        if prediction in attr_mapping_dict:
            pred_key = attr_mapping_dict[prediction]
            if pred_key in exist_ent_index:
                for key, value in attr_mapping_dict.items():
                    if value == non_exist_ent_index[0]:
                        return key
            else:
                return prediction
        else:
            return prediction
Ejemplo n.º 18
0
 def __init__(self):
     self.extractor = EntityExtractor()
     self.searcher = AnswerSearching()
     self.answer = "对不起,您的问题我不知道,我今后会努力改进的。"